diff options
author | Seppo Jaakola <seppo.jaakola@codership.com> | 2013-12-04 10:32:43 +0200 |
---|---|---|
committer | Seppo Jaakola <seppo.jaakola@codership.com> | 2013-12-04 10:32:43 +0200 |
commit | 496e22cf3bd2a481fd3502d86e5a4e8228bf9823 (patch) | |
tree | 80549f8005fcf3236bfa004a5aea35e4e67b36ca /sql | |
parent | 45f484b8381a5923aec9c704e54c7f7bcfa02a40 (diff) | |
parent | 26f56089c734852dc31d98fd73e1d8f1750bd1a8 (diff) | |
download | mariadb-git-496e22cf3bd2a481fd3502d86e5a4e8228bf9823.tar.gz |
merge with MariaDB 5.6 bzr merge lp:maria --rtag:mariadb-10.0.6
and a number of fixes to make this buildable.
Run also few short multi-master high conflict rate tests, with no issues
Diffstat (limited to 'sql')
146 files changed, 17538 insertions, 5756 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index fd654d01b1d..697a4e0824f 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -20,7 +20,8 @@ ENDIF() INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/sql -${CMAKE_SOURCE_DIR}/regex +${CMAKE_BINARY_DIR}/pcre +${CMAKE_SOURCE_DIR}/pcre ${ZLIB_INCLUDE_DIR} ${SSL_INCLUDE_DIRS} ${CMAKE_BINARY_DIR}/sql @@ -30,7 +31,6 @@ ${WSREP_INCLUDES} SET(GEN_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.h ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc -${CMAKE_CURRENT_BINARY_DIR}/sql_builtin.cc ${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h ) @@ -102,6 +102,7 @@ SET (SQL_SOURCE transaction.cc sys_vars.cc sql_truncate.cc datadict.cc sql_reload.cc sql_cmd.h # added in MariaDB: + sql_explain.h sql_explain.cc sql_lifo_buffer.h sql_join_cache.h sql_join_cache.cc create_options.cc multi_range_read.cc opt_index_cond_pushdown.cc opt_subselect.cc @@ -110,9 +111,10 @@ SET (SQL_SOURCE threadpool_common.cc ../sql-common/mysql_async.c my_apc.cc my_apc.h - rpl_gtid.cc + rpl_gtid.cc rpl_parallel.cc ${WSREP_SOURCES} table_cache.cc + ${CMAKE_CURRENT_BINARY_DIR}/sql_builtin.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE}) @@ -129,8 +131,8 @@ ADD_LIBRARY(sql STATIC ${SQL_SOURCE}) ADD_DEPENDENCIES(sql GenServerSource) DTRACE_INSTRUMENT(sql) TARGET_LINK_LIBRARIES(sql ${MYSQLD_STATIC_PLUGIN_LIBS} - mysys mysys_ssl dbug strings vio regex - ${LIBWRAP} ${LIBCRYPT} ${LIBDL} + mysys mysys_ssl dbug strings vio pcre ${LIBJEMALLOC} + ${LIBWRAP} ${LIBCRYPT} ${LIBDL} ${CMAKE_THREAD_LIBS_INIT} ${WSREP_LIB} ${SSL_LIBRARIES}) diff --git a/sql/authors.h b/sql/authors.h index 84c29f8c127..406715ab074 100644 --- a/sql/authors.h +++ b/sql/authors.h @@ -31,41 +31,64 @@ 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. */ 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)" }, + "Unicode and character sets" }, + { "Alexey Botchkov (Holyfoot)", "Izhevsk, Russia", + "GIS extensions, embedded server, precision math"}, + { "Daniel Bartholomew", "Raleigh, USA", "MariaDB documentation"}, + { "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" }, + { "Percona", "CA, USA", "XtraDB, microslow patches, extensions to slow log"}, + + /* People working on MySQL code base (not NDB) */ { "Guilhem Bichot", "Bordeaux, France", "Replication (since 4.0)" }, - { "Konstantin Osipov", "Moscow, Russia", - "Prepared statements (4.1), Cursors (5.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 :)" }, + { "Konstantin Osipov", "Moscow, Russia", + "Prepared statements (4.1), Cursors (5.0)" }, + { "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 +104,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 +118,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 +146,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 +157,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 +175,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/client_settings.h b/sql/client_settings.h index 54cb72f9412..5707413f69f 100644 --- a/sql/client_settings.h +++ b/sql/client_settings.h @@ -30,11 +30,11 @@ */ #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_CONNECT_ATTRS) #define read_user_name(A) {} #undef _CUSTOMCONFIG_ diff --git a/sql/contributors.h b/sql/contributors.h index b1ba2c20997..1859c5d48c5 100644 --- a/sql/contributors.h +++ b/sql/contributors.h @@ -30,12 +30,21 @@ struct show_table_contributors_st { Get permission before editing. - IMPORTANT: Names should be left in historical order. - Names should be encoded using UTF-8. */ struct show_table_contributors_st show_table_contributors[]= { + /* MariaDB foundation members, in contribution size order */ + {"Booking.com", "http://www.booking.com", "Founding member of the MariaDB foundation"}, + {"SkySQL Ab", "http://www.skysql.com", "Founding member of the MariaDB foundation"}, + {"Parallels", "http://www.parallels.com/products/plesk", "Founding member of the MariaDB foundation"}, + + /* Smaller sponsors, newer per year */ + {"Jelastic.com", "Russia", "Sponsor of the MariaDB foundation"}, + {"Planetta.net", "Finland", "Sponsor of the MariaDB foundation"}, + {"Open query", "Australia", "Sponsor of the MariaDB foundation"}, + + /* 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 774d739f153..d9b1d31fd62 100644 --- a/sql/create_options.cc +++ b/sql/create_options.cc @@ -115,6 +115,8 @@ static bool report_unknown_option(THD *thd, engine_option_value *val, DBUG_RETURN(FALSE); } +#define value_ptr(STRUCT,OPT) ((char*)(STRUCT) + (OPT)->offset) + static bool set_one_value(ha_create_table_option *opt, THD *thd, const LEX_STRING *value, void *base, bool suppress_warning, @@ -131,7 +133,7 @@ static bool set_one_value(ha_create_table_option *opt, 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; @@ -155,7 +157,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; @@ -168,7 +170,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) @@ -200,7 +202,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) @@ -284,7 +286,7 @@ bool parse_option_list(THD* thd, handlerton *hton, void *option_struct_arg, *option_struct= alloc_root(root, option_struct_size); } - for (opt= rules; opt && opt->name; opt++) + for (opt= rules; rules && opt->name; opt++) { bool seen=false; for (val= *option_list; val; val= val->next) @@ -362,7 +364,7 @@ bool parse_option_list(THD* thd, handlerton *hton, void *option_struct_arg, */ static bool resolve_sysvars(handlerton *hton, ha_create_table_option *rules) { - for (ha_create_table_option *opt= rules; opt && opt->name; opt++) + for (ha_create_table_option *opt= rules; rules && opt->name; opt++) { if (opt->type == HA_OPTION_TYPE_SYSVAR) { @@ -428,7 +430,7 @@ bool resolve_sysvar_table_options(handlerton *hton) */ static void free_sysvars(handlerton *hton, ha_create_table_option *rules) { - for (ha_create_table_option *opt= rules; opt && opt->name; opt++) + for (ha_create_table_option *opt= rules; rules && opt->name; opt++) { if (opt->var) { @@ -491,6 +493,26 @@ bool parse_engine_table_options(THD *thd, handlerton *ht, TABLE_SHARE *share) } +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 */ diff --git a/sql/create_options.h b/sql/create_options.h index c7fac8b37fd..d6b48822c49 100644 --- a/sql/create_options.h +++ b/sql/create_options.h @@ -96,4 +96,7 @@ 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); #endif diff --git a/sql/datadict.cc b/sql/datadict.cc index 4bc74af7bdb..deeedcd512d 100644 --- a/sql/datadict.cc +++ b/sql/datadict.cc @@ -18,6 +18,22 @@ #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. @@ -36,6 +52,7 @@ frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) File file; uchar header[10]; //"TYPE=VIEW\n" it is 10 characters size_t error; + frm_type_enum type= FRMTYPE_ERROR; DBUG_ENTER("dd_frm_type"); *dbt= DB_TYPE_UNKNOWN; @@ -43,12 +60,16 @@ frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) 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)); - mysql_file_close(file, MYF(MY_WME)); if (error) - DBUG_RETURN(FRMTYPE_ERROR); + goto err; if (!strncmp((char*) header, "TYPE=VIEW\n", sizeof(header))) - DBUG_RETURN(FRMTYPE_VIEW); + { + type= FRMTYPE_VIEW; + goto err; + } + + type= FRMTYPE_TABLE; /* This is just a check for DB_TYPE. We'll return default unknown type @@ -56,17 +77,57 @@ frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) on return value from this function (default FRMTYPE_TABLE) */ if (!is_binary_frm_header(header)) - DBUG_RETURN(FRMTYPE_TABLE); + goto err; - /* - XXX this is a bug. - if header[3] is > DB_TYPE_FIRST_DYNAMIC, then the complete - storage engine name must be read from the frm - */ *dbt= (enum legacy_db_type) (uint) *(header + 3); + if (*dbt >= DB_TYPE_FIRST_DYNAMIC) /* read the true engine name */ + { + MY_STAT state; + uchar *frm_image= 0; + uint n_length; + + if (mysql_file_fstat(file, &state, MYF(MY_WME))) + goto err; + + if (mysql_file_seek(file, 0, SEEK_SET, MYF(MY_WME))) + goto err; + + if (read_string(file, &frm_image, state.st_size)) + goto err; + + if ((n_length= uint4korr(frm_image+55))) + { + uint record_offset= uint2korr(frm_image+6)+ + ((uint2korr(frm_image+14) == 0xffff ? + uint4korr(frm_image+47) : uint2korr(frm_image+14))); + uint reclength= uint2korr(frm_image+16); + + uchar *next_chunk= frm_image + record_offset + reclength; + uchar *buff_end= next_chunk + n_length; + uint connect_string_length= uint2korr(next_chunk); + 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; + } + } + + my_free(frm_image); + } + /* Probably a table. */ - DBUG_RETURN(FRMTYPE_TABLE); +err: + mysql_file_close(file, MYF(MY_WME)); + DBUG_RETURN(type); } diff --git a/sql/debug_sync.cc b/sql/debug_sync.cc index 750f770552e..35b838d7c3d 100644 --- a/sql/debug_sync.cc +++ b/sql/debug_sync.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2009, 2011, Oracle and/or its affiliates. +/* Copyright (c) 2009, 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 @@ -1394,7 +1394,7 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) if (action->wait_for.length()) { mysql_mutex_t *old_mutex; - mysql_cond_t *UNINIT_VAR(old_cond); + mysql_cond_t *old_cond= NULL; int error= 0; struct timespec abstime; diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index e236319d757..d65180a60be 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -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, 0); + mysql_reset_thd_for_next_command(thd); /* MySQL parser currently assumes that current database is either diff --git a/sql/events.cc b/sql/events.cc index 73ce894095c..ea62ec4129d 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -1169,7 +1169,7 @@ end: } #ifdef WITH_WSREP -int wsrep_create_event_query(THD *thd, uchar** buf, int* buf_len) +int wsrep_create_event_query(THD *thd, uchar** buf, size_t* buf_len) { String log_query; diff --git a/sql/field.cc b/sql/field.cc index 81b5a66d908..755d2f8b625 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1,6 +1,6 @@ /* - Copyright (c) 2000, 2012, Oracle and/or its affiliates. - Copyright (c) 2008, 2013, Monty Program Ab + Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2008, 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 @@ -2816,7 +2816,7 @@ int Field_new_decimal::store(const char *from, uint length, &decimal_value)) && thd->abort_on_warning) { - ErrConvString errmsg(from, length, &my_charset_bin); + ErrConvString errmsg(from, length, charset_arg); push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), @@ -2836,7 +2836,7 @@ int Field_new_decimal::store(const char *from, uint length, break; case E_DEC_BAD_NUM: { - ErrConvString errmsg(from, length, &my_charset_bin); + ErrConvString errmsg(from, length, charset_arg); push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_TRUNCATED_WRONG_VALUE_FOR_FIELD, ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD), @@ -6509,7 +6509,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); } @@ -6966,9 +6972,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); } @@ -7274,7 +7284,7 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) if (!String::needs_conversion(length, cs, field_charset, &dummy_offset)) { Field_blob::store_length(length); - bmove(ptr+packlength,(char*) &from,sizeof(char*)); + bmove(ptr + packlength, &from, sizeof(char*)); return 0; } if (tmpstr.copy(from, length, cs)) @@ -7583,8 +7593,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); } } @@ -7793,7 +7806,7 @@ int Field_geom::store(const char *from, uint length, CHARSET_INFO *cs) value.copy(from, length, cs); from= value.ptr(); } - bmove(ptr + packlength, (char*) &from, sizeof(char*)); + bmove(ptr + packlength, &from, sizeof(char*)); } return 0; diff --git a/sql/field.h b/sql/field.h index e6a3b9c530b..72be8e3519a 100644 --- a/sql/field.h +++ b/sql/field.h @@ -126,6 +126,36 @@ inline enum_field_types real_type_to_type(enum_field_types 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; + default: return MYSQL_TIMESTAMP_ERROR; + } +} + + +/** + Tests if field type is temporal, i.e. represents + DATE, TIME, DATETIME or TIMESTAMP types in SQL. + + @param type Field type, as returned by field->type(). + @retval true If field type is temporal + @retval false If field type is not temporal +*/ +inline bool is_temporal_type(enum_field_types type) +{ + return mysql_type_to_time_type(type) != MYSQL_TIMESTAMP_ERROR; +} + /* Virtual_column_info is the class to contain additional characteristics that is specific for a virtual/computed @@ -368,7 +398,7 @@ public: DBUG_ENTER("Field::pack_length_from_metadata"); DBUG_RETURN(field_metadata); } - virtual uint row_pack_length() { return 0; } + virtual uint row_pack_length() const { return 0; } virtual int save_field_metadata(uchar *first_byte) { return do_save_field_metadata(first_byte); } @@ -975,7 +1005,7 @@ public: int store_decimal(const my_decimal *); my_decimal *val_decimal(my_decimal *); uint is_equal(Create_field *new_field); - uint row_pack_length() { return pack_length(); } + uint row_pack_length() const { return pack_length(); } uint32 pack_length_from_metadata(uint field_metadata) { uint32 length= pack_length(); DBUG_PRINT("result", ("pack_length_from_metadata(%d): %u", @@ -1151,7 +1181,7 @@ public: uint size_of() const { return sizeof(*this); } uint32 pack_length() const { return (uint32) bin_size; } uint pack_length_from_metadata(uint field_metadata); - uint row_pack_length() { return pack_length(); } + uint row_pack_length() const { return pack_length(); } bool compatible_field_size(uint field_metadata, Relay_log_info *rli, uint16 mflags, int *order_var); uint is_equal(Create_field *new_field); @@ -1400,7 +1430,7 @@ public: int cmp(const uchar *,const uchar *); void sort_string(uchar *buff,uint length); uint32 pack_length() const { return sizeof(float); } - uint row_pack_length() { return pack_length(); } + uint row_pack_length() const { return pack_length(); } void sql_type(String &str) const; private: int do_save_field_metadata(uchar *first_byte); @@ -1440,7 +1470,7 @@ public: int cmp(const uchar *,const uchar *); void sort_string(uchar *buff,uint length); uint32 pack_length() const { return sizeof(double); } - uint row_pack_length() { return pack_length(); } + uint row_pack_length() const { return pack_length(); } void sql_type(String &str) const; private: int do_save_field_metadata(uchar *first_byte); @@ -1694,7 +1724,7 @@ public: { return my_timestamp_binary_length(dec); } - uint row_pack_length() { return pack_length(); } + uint row_pack_length() const { return pack_length(); } uint pack_length_from_metadata(uint field_metadata) { DBUG_ENTER("Field_timestampf::pack_length_from_metadata"); @@ -1902,7 +1932,7 @@ public: { return my_time_binary_length(dec); } - uint row_pack_length() { return pack_length(); } + uint row_pack_length() const { return pack_length(); } uint pack_length_from_metadata(uint field_metadata) { DBUG_ENTER("Field_timef::pack_length_from_metadata"); @@ -2062,7 +2092,7 @@ public: { return my_datetime_binary_length(dec); } - uint row_pack_length() { return pack_length(); } + uint row_pack_length() const { return pack_length(); } uint pack_length_from_metadata(uint field_metadata) { DBUG_ENTER("Field_datetimef::pack_length_from_metadata"); @@ -2177,7 +2207,7 @@ public: } bool compatible_field_size(uint field_metadata, Relay_log_info *rli, uint16 mflags, int *order_var); - uint row_pack_length() { return field_length; } + uint row_pack_length() const { return field_length; } int pack_cmp(const uchar *a,const uchar *b,uint key_length, bool insert_or_update); int pack_cmp(const uchar *b,uint key_length,bool insert_or_update); @@ -2226,7 +2256,7 @@ public: enum_field_types type() const { return MYSQL_TYPE_VARCHAR; } enum ha_base_keytype key_type() const; - uint row_pack_length() { return field_length; } + uint row_pack_length() const { return field_length; } bool zero_pack() const { return 0; } int reset(void) { bzero(ptr,field_length+length_bytes); return 0; } uint32 pack_length() const { return (uint32) field_length+length_bytes; } @@ -2356,7 +2386,7 @@ public: */ uint32 pack_length_no_ptr() const { return (uint32) (packlength); } - uint row_pack_length() { return pack_length_no_ptr(); } + 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 @@ -2521,7 +2551,7 @@ public: enum_field_types real_type() const { return MYSQL_TYPE_ENUM; } uint pack_length_from_metadata(uint field_metadata) { return (field_metadata & 0x00ff); } - uint row_pack_length() { return pack_length(); } + uint row_pack_length() const { return pack_length(); } virtual bool zero_pack() const { return 0; } bool optimize_range(uint idx, uint part) { return 0; } bool eq_def(Field *field); @@ -2672,7 +2702,7 @@ public: uint32 pack_length() const { return (uint32) (field_length + 7) / 8; } uint32 pack_length_in_rec() const { return bytes_in_rec; } uint pack_length_from_metadata(uint field_metadata); - uint row_pack_length() + uint row_pack_length() const { return (bytes_in_rec + ((bit_len > 0) ? 1 : 0)); } bool compatible_field_size(uint metadata, Relay_log_info *rli, uint16 mflags, int *order_var); diff --git a/sql/filesort.cc b/sql/filesort.cc index 7cb2306eb7c..8783aa96739 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -932,8 +932,12 @@ static void make_sortkey(register Sort_param *param, memcpy(param->tmp_buffer,from,length); from=param->tmp_buffer; } - tmp_length= my_strnxfrm(cs,to,sort_field->length, - (uchar*) from, length); + tmp_length= cs->coll->strnxfrm(cs, to, sort_field->length, + item->max_char_length() * + cs->strxfrm_multiply, + (uchar*) from, length, + MY_STRXFRM_PAD_WITH_SPACE | + MY_STRXFRM_PAD_TO_MAXLEN); DBUG_ASSERT(tmp_length == sort_field->length); } else diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index c3d28f91c27..c5466532c6a 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -297,6 +297,7 @@ ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share, m_clone_mem_root= clone_mem_root_arg; 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; } diff --git a/sql/handler.cc b/sql/handler.cc index b060c06150d..4816f5f6ae0 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -530,6 +530,12 @@ int ha_initialize_handlerton(st_plugin_int *plugin) { uint tmp; ulong fslot; + + DBUG_EXECUTE_IF("unstable_db_type", { + static int i= (int) DB_TYPE_FIRST_DYNAMIC; + hton->db_type= (enum legacy_db_type)++i; + }); + /* now check the db_type for conflict */ if (hton->db_type <= DB_TYPE_UNKNOWN || hton->db_type >= DB_TYPE_DEFAULT || @@ -1253,6 +1259,8 @@ int ha_commit_trans(THD *thd, bool all) 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", @@ -1317,6 +1325,8 @@ int ha_commit_trans(THD *thd, bool all) /* rw_trans is TRUE when we in a transaction changing data */ bool rw_trans= is_real_trans && (rw_ha_count > 0); 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) { @@ -1494,8 +1504,11 @@ int ha_commit_one_phase(THD *thd, bool all) transaction.all.ha_list, see why in trans_register_ha()). */ bool is_real_trans=all || thd->transaction.all.ha_list == 0; + int res; DBUG_ENTER("ha_commit_one_phase"); - int res= commit_one_phase_2(thd, all, trans, is_real_trans); + if (is_real_trans && (res= thd->wait_for_prior_commit())) + DBUG_RETURN(res); + res= commit_one_phase_2(thd, all, trans, is_real_trans); DBUG_RETURN(res); } @@ -1535,7 +1548,11 @@ 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->transaction.cleanup(); + { + thd->wakeup_subsequent_commits(error); + thd->transaction.cleanup(); + } + DBUG_RETURN(error); } @@ -1609,7 +1626,10 @@ int ha_rollback_trans(THD *thd, bool all) } /* Always cleanup. Even if nht==0. There may be savepoints. */ if (is_real_trans) - thd->transaction.cleanup(); + { + thd->wakeup_subsequent_commits(error); + thd->transaction.cleanup(); + } if (all) thd->transaction_rollback_request= FALSE; @@ -3558,7 +3578,6 @@ bool handler::get_error_message(int error, String* buf) return FALSE; } - /** Check for incompatible collation changes. @@ -3599,9 +3618,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; } @@ -3612,6 +3632,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 */ @@ -4974,10 +4997,9 @@ static int cmp_table_names(LEX_STRING * const *a, LEX_STRING * const *b) Discovered_table_list::Discovered_table_list(THD *thd_arg, Dynamic_array<LEX_STRING*> *tables_arg, - const LEX_STRING *wild_arg) + const LEX_STRING *wild_arg) : + thd(thd_arg), with_temps(false), tables(tables_arg) { - thd= thd_arg; - tables= tables_arg; if (wild_arg->str && wild_arg->str[0]) { wild= wild_arg->str; @@ -4989,6 +5011,12 @@ Discovered_table_list::Discovered_table_list(THD *thd_arg, bool Discovered_table_list::add_table(const char *tname, size_t tlen) { + /* + 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(files_charset_info, tname, tname + tlen, wild, wend, wild_prefix, wild_one, wild_many)) return 0; @@ -5001,8 +5029,13 @@ bool Discovered_table_list::add_table(const char *tname, size_t tlen) bool Discovered_table_list::add_file(const char *fname) { + 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)); + size_t tlen= filename_to_tablename(fname, tname, sizeof(tname), is_temp); return add_table(tname, tlen); } @@ -5061,6 +5094,22 @@ static my_bool discover_names(THD *thd, plugin_ref plugin, return 0; } +/** + 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) { diff --git a/sql/handler.h b/sql/handler.h index 7108879ac46..9d351a66f2d 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -430,7 +430,8 @@ enum legacy_db_type DB_TYPE_PERFORMANCE_SCHEMA=28, DB_TYPE_WSREP=41, DB_TYPE_ARIA=42, - DB_TYPE_FIRST_DYNAMIC=43, + DB_TYPE_TOKUDB=43, + DB_TYPE_FIRST_DYNAMIC=44, DB_TYPE_DEFAULT=127 // Must be last }; /* @@ -712,12 +713,15 @@ struct TABLE; */ enum enum_schema_tables { - SCH_CHARSETS= 0, + SCH_ALL_PLUGINS, + SCH_APPLICABLE_ROLES, + SCH_CHARSETS, SCH_CLIENT_STATS, SCH_COLLATIONS, SCH_COLLATION_CHARACTER_SET_APPLICABILITY, SCH_COLUMNS, SCH_COLUMN_PRIVILEGES, + SCH_ENABLED_ROLES, SCH_ENGINES, SCH_EVENTS, SCH_EXPLAIN, @@ -731,7 +735,6 @@ enum enum_schema_tables SCH_PARAMETERS, SCH_PARTITIONS, SCH_PLUGINS, - SCH_ALL_PLUGINS, SCH_PROCESSLIST, SCH_PROFILES, SCH_REFERENTIAL_CONSTRAINTS, @@ -887,8 +890,8 @@ enum ha_option_type { HA_OPTION_TYPE_ULL, /* unsigned long long */ HA_xOPTION_STRING(name, ha_index_option_struct, field) #define HA_IOPTION_ENUM(name, field, values, def) \ HA_xOPTION_ENUM(name, ha_index_option_struct, field, values, def) -#define HA_IOPTION_BOOL(name, field, values, def) \ - HA_xOPTION_BOOL(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 @@ -1374,6 +1377,7 @@ static inline sys_var *find_hton_sysvar(handlerton *hton, st_mysql_sys_var *var) Schema which have no meaning for replication. */ #define HTON_NO_BINLOG_ROW_OPT (1 << 9) +#define HTON_EXTENDED_KEYS (1 <<10) //supports extended keys class Ha_trx_info; @@ -2555,6 +2559,16 @@ public: } /* This is called after index_init() if we need to do a index scan */ virtual int prepare_index_scan() { return 0; } + virtual int prepare_index_key_scan_map(const uchar * key, key_part_map keypart_map) + { + uint key_len= calculate_key_len(table, active_index, key, keypart_map); + return prepare_index_key_scan(key, key_len); + } + virtual int prepare_index_key_scan( const uchar * key, uint key_len ) + { return 0; } + virtual int prepare_range_scan(const key_range *start_key, const key_range *end_key) + { return 0; } + int ha_rnd_init(bool scan) __attribute__ ((warn_unused_result)) { DBUG_EXECUTE_IF("ha_rnd_init_fail", return HA_ERR_TABLE_DEF_CHANGED;); @@ -3629,6 +3643,7 @@ public: virtual bool check_if_supported_virtual_columns(void) { return FALSE;} TABLE* get_table() { return table; } + TABLE_SHARE* get_table_share() { return table_share; } protected: /* deprecated, don't use in new engines */ inline void ha_statistic_increment(ulong SSV::*offset) const { } @@ -3880,7 +3895,7 @@ protected: #include "multi_range_read.h" -bool key_uses_partial_cols(TABLE *table, uint keyno); +bool key_uses_partial_cols(TABLE_SHARE *table, uint keyno); /* Some extern variables used with handlers */ @@ -3958,11 +3973,14 @@ 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); diff --git a/sql/item.cc b/sql/item.cc index 441470a0df1..248b0899026 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -263,6 +263,24 @@ String *Item::val_str_ascii(String *str) } +String *Item::val_str(String *str, String *converter, CHARSET_INFO *cs) +{ + String *res= val_str(str); + if (null_value) + return (String *) 0; + + if (!cs) + return res; + + uint errors; + if ((null_value= converter->copy(res->ptr(), res->length(), + collation.collation, cs, &errors))) + return (String *) 0; + + return converter; +} + + String *Item::val_string_from_real(String *str) { double nr= val_real(); @@ -297,7 +315,9 @@ String *Item::val_string_from_decimal(String *str) String *Item::val_string_from_date(String *str) { MYSQL_TIME ltime; - if (get_date(<ime, sql_mode_for_dates(current_thd)) || + if (get_date(<ime, field_type() == MYSQL_TYPE_TIME + ? TIME_TIME_ONLY + : sql_mode_for_dates(current_thd)) || str->alloc(MAX_DATE_STRING_REP_LENGTH)) { null_value= 1; @@ -555,6 +575,28 @@ uint Item::decimal_precision() const } +uint Item::temporal_precision(enum_field_types type) +{ + if (const_item() && result_type() == STRING_RESULT && + !is_temporal_type(field_type())) + { + MYSQL_TIME ltime; + String buf, *tmp; + MYSQL_TIME_STATUS status; + DBUG_ASSERT(fixed); + if ((tmp= val_str(&buf)) && + (type == MYSQL_TYPE_TIME ? + str_to_time(tmp->charset(), tmp->ptr(), tmp->length(), + <ime, TIME_TIME_ONLY, &status) : + str_to_datetime(tmp->charset(), tmp->ptr(), tmp->length(), + <ime, TIME_FUZZY_DATES, &status)) > + MYSQL_TIMESTAMP_ERROR) + return MY_MIN(status.precision, TIME_SECOND_PART_DIGITS); + } + return MY_MIN(decimals, TIME_SECOND_PART_DIGITS); +} + + void Item::print_item_w_name(String *str, enum_query_type query_type) { print(str, query_type); @@ -5105,6 +5147,11 @@ bool Item_field::fix_fields(THD *thd, Item **reference) goto mark_non_agg_field; } + if (thd->lex->in_sum_func && + thd->lex->in_sum_func->nest_level == + thd->lex->current_select->nest_level) + set_if_bigger(thd->lex->in_sum_func->max_arg_level, + thd->lex->current_select->nest_level); /* if it is not expression from merged VIEW we will set this field. @@ -5121,11 +5168,6 @@ bool Item_field::fix_fields(THD *thd, Item **reference) return FALSE; set_field(from_field); - if (thd->lex->in_sum_func && - thd->lex->in_sum_func->nest_level == - thd->lex->current_select->nest_level) - set_if_bigger(thd->lex->in_sum_func->max_arg_level, - thd->lex->current_select->nest_level); } else if (thd->mark_used_columns != MARK_COLUMNS_NONE) { @@ -9696,18 +9738,10 @@ table_map Item_ref::used_tables() const } -void Item_ref::update_used_tables() +void Item_ref::update_used_tables() { if (!get_depended_from()) (*ref)->update_used_tables(); - maybe_null|= (*ref)->maybe_null; -} - -void Item_direct_view_ref::update_used_tables() -{ - Item_ref::update_used_tables(); - if (view->table && view->table->maybe_null) - maybe_null= TRUE; } table_map Item_direct_view_ref::used_tables() const diff --git a/sql/item.h b/sql/item.h index e0f2d26ba14..f5917f7d9c3 100644 --- a/sql/item.h +++ b/sql/item.h @@ -908,6 +908,10 @@ public: virtual String *val_str_ascii(String *str); /* + Returns the val_str() value converted to the given character set. + */ + String *val_str(String *str, String *converter, CHARSET_INFO *to); + /* Return decimal representation of item with fixed point. SYNOPSIS @@ -1022,6 +1026,10 @@ public: virtual uint decimal_precision() const; inline int decimal_int_part() const { return my_decimal_int_part(decimal_precision(), decimals); } + /** + TIME or DATETIME precision of the item: 0..6 + */ + uint temporal_precision(enum_field_types type); /* Returns true if this is constant (during query execution, i.e. its value will not change until next fix_fields) and its value is known. @@ -1191,6 +1199,7 @@ public: virtual bool view_used_tables_processor(uchar *arg) { return 0; } virtual bool eval_not_null_tables(uchar *opt_arg) { return 0; } 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; @@ -2101,8 +2110,6 @@ public: void update_used_tables() { update_table_bitmaps(); - if (field && field->table) - maybe_null|= field->maybe_null(); } Item *get_tmp_table_item(THD *thd); bool collect_item_field_processor(uchar * arg); @@ -3350,7 +3357,6 @@ public: void update_used_tables() { orig_item->update_used_tables(); - maybe_null|= orig_item->maybe_null; } bool const_item() const { return orig_item->const_item(); } table_map not_null_tables() const { return orig_item->not_null_tables(); } @@ -3443,7 +3449,6 @@ public: Item *replace_equal_field(uchar *arg); table_map used_tables() const; table_map not_null_tables() const; - void update_used_tables(); bool walk(Item_processor processor, bool walk_subquery, uchar *arg) { return (*ref)->walk(processor, walk_subquery, arg) || diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 33b94ece45d..e6688a5daa9 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -1354,7 +1354,7 @@ int Arg_comparator::compare_e_row() void Item_func_truth::fix_length_and_dec() { - set_persist_maybe_null(0); + maybe_null= 0; null_value= 0; decimals= 0; max_length= 1; @@ -1435,11 +1435,14 @@ bool Item_in_optimizer::eval_not_null_tables(uchar *opt_arg) return FALSE; } + bool Item_in_optimizer::fix_left(THD *thd) { + DBUG_ENTER("Item_in_optimizer::fix_left"); if ((!args[0]->fixed && args[0]->fix_fields(thd, args)) || (!cache && !(cache= Item_cache::get_cache(args[0])))) - return 1; + DBUG_RETURN(1); + DBUG_PRINT("info", ("actual fix fields")); cache->setup(args[0]); if (cache->cols() == 1) @@ -1465,11 +1468,15 @@ bool Item_in_optimizer::fix_left(THD *thd) { my_error(ER_NOT_SUPPORTED_YET, MYF(0), "SUBQUERY in ROW in left expression of IN/ALL/ANY"); - return 1; + DBUG_RETURN(1); } Item *element=args[0]->element_index(i); if (element->used_tables() || !element->const_item()) - ((Item_cache *)cache->element_index(i))->set_used_tables(OUTER_REF_TABLE_BIT); + { + ((Item_cache *)cache->element_index(i))-> + set_used_tables(OUTER_REF_TABLE_BIT); + cache->set_used_tables(OUTER_REF_TABLE_BIT); + } else ((Item_cache *)cache->element_index(i))->set_used_tables(0); } @@ -1490,7 +1497,7 @@ bool Item_in_optimizer::fix_left(THD *thd) with_sum_func= with_sum_func || args[1]->with_sum_func; const_item_cache= const_item_cache && args[1]->const_item(); } - return 0; + DBUG_RETURN(0); } @@ -1906,8 +1913,7 @@ longlong Item_func_eq::val_int() void Item_func_equal::fix_length_and_dec() { Item_bool_func2::fix_length_and_dec(); - set_persist_maybe_null(0); - null_value= 0; + maybe_null=null_value=0; } longlong Item_func_equal::val_int() @@ -2006,7 +2012,7 @@ void Item_func_interval::fix_length_and_dec() for (uint i= 1; not_null_consts && i < rows; i++) { Item *el= row->element_index(i); - not_null_consts&= el->const_item() & !el->is_null(); + not_null_consts&= el->const_item() && !el->is_null(); } if (not_null_consts && @@ -2046,7 +2052,7 @@ void Item_func_interval::fix_length_and_dec() } } } - set_persist_maybe_null(0); + maybe_null= 0; max_length= 2; used_tables_cache|= row->used_tables(); not_null_tables_cache= row->not_null_tables(); @@ -2206,6 +2212,15 @@ bool Item_func_between::eval_not_null_tables(uchar *opt_arg) } +bool Item_func_between::count_sargable_conds(uchar *arg) +{ + SELECT_LEX *sel= (SELECT_LEX *) arg; + sel->cond_count++; + sel->between_count++; + return 0; +} + + void Item_func_between::fix_after_pullout(st_select_lex *new_parent, Item **ref) { /* This will re-calculate attributes of the arguments */ @@ -2416,12 +2431,13 @@ void Item_func_ifnull::fix_length_and_dec() { uint32 char_length; - agg_result_type(&hybrid_type, args, 2); + agg_result_type(&cached_result_type, args, 2); + cached_field_type= agg_field_type(args, 2); maybe_null=args[1]->maybe_null; decimals= MY_MAX(args[0]->decimals, args[1]->decimals); unsigned_flag= args[0]->unsigned_flag && args[1]->unsigned_flag; - if (hybrid_type == DECIMAL_RESULT || hybrid_type == INT_RESULT) + 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); @@ -2434,9 +2450,9 @@ Item_func_ifnull::fix_length_and_dec() else char_length= MY_MAX(args[0]->max_char_length(), args[1]->max_char_length()); - switch (hybrid_type) { + switch (cached_result_type) { case STRING_RESULT: - if (agg_arg_charsets_for_comparison(collation, args, arg_count)) + if (count_string_result_length(cached_field_type, args, arg_count)) return; break; case DECIMAL_RESULT: @@ -2451,7 +2467,6 @@ Item_func_ifnull::fix_length_and_dec() DBUG_ASSERT(0); } fix_char_length(char_length); - cached_field_type= agg_field_type(args, 2); } @@ -2465,11 +2480,6 @@ uint Item_func_ifnull::decimal_precision() const } -enum_field_types Item_func_ifnull::field_type() const -{ - return cached_field_type; -} - Field *Item_func_ifnull::tmp_table_field(TABLE *table) { return tmp_table_field_from_field_type(table, 0); @@ -2543,6 +2553,18 @@ 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)) + return (null_value= false); + if (!args[1]->get_date(ltime, fuzzydate & ~TIME_FUZZY_DATES)) + return (null_value= false); + bzero((char*) ltime,sizeof(*ltime)); + return null_value= !(fuzzydate & TIME_FUZZY_DATES); +} + + /** Perform context analysis of an IF item tree. @@ -2637,20 +2659,20 @@ Item_func_if::fix_length_and_dec() } 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= MY_MAX(args[1]->decimals, args[2]->decimals); unsigned_flag=args[1]->unsigned_flag && args[2]->unsigned_flag; if (cached_result_type == STRING_RESULT) { - if (agg_arg_charsets_for_string_result(collation, args + 1, 2)) - return; + count_string_result_length(cached_field_type, args + 1, 2); + return; } else { collation.set_numeric(); // Number } - cached_field_type= agg_field_type(args + 1, 2); uint32 char_length; if ((cached_result_type == DECIMAL_RESULT ) @@ -2680,7 +2702,7 @@ uint Item_func_if::decimal_precision() const double -Item_func_if::val_real() +Item_func_if::real_op() { DBUG_ASSERT(fixed == 1); Item *arg= args[0]->val_bool() ? args[1] : args[2]; @@ -2690,7 +2712,7 @@ Item_func_if::val_real() } longlong -Item_func_if::val_int() +Item_func_if::int_op() { DBUG_ASSERT(fixed == 1); Item *arg= args[0]->val_bool() ? args[1] : args[2]; @@ -2700,7 +2722,7 @@ Item_func_if::val_int() } String * -Item_func_if::val_str(String *str) +Item_func_if::str_op(String *str) { DBUG_ASSERT(fixed == 1); Item *arg= args[0]->val_bool() ? args[1] : args[2]; @@ -2713,7 +2735,7 @@ Item_func_if::val_str(String *str) my_decimal * -Item_func_if::val_decimal(my_decimal *decimal_value) +Item_func_if::decimal_op(my_decimal *decimal_value) { DBUG_ASSERT(fixed == 1); Item *arg= args[0]->val_bool() ? args[1] : args[2]; @@ -2723,11 +2745,19 @@ Item_func_if::val_decimal(my_decimal *decimal_value) } +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)); +} + + void Item_func_nullif::fix_length_and_dec() { Item_bool_func2::fix_length_and_dec(); - set_persist_maybe_null(1); + maybe_null=1; if (args[0]) // Only false if EOM { max_length=args[0]->max_length; @@ -2881,7 +2911,7 @@ Item *Item_func_case::find_item(String *str) } -String *Item_func_case::val_str(String *str) +String *Item_func_case::str_op(String *str) { DBUG_ASSERT(fixed == 1); String *res; @@ -2899,7 +2929,7 @@ String *Item_func_case::val_str(String *str) } -longlong Item_func_case::val_int() +longlong Item_func_case::int_op() { DBUG_ASSERT(fixed == 1); char buff[MAX_FIELD_WIDTH]; @@ -2917,7 +2947,7 @@ longlong Item_func_case::val_int() return res; } -double Item_func_case::val_real() +double Item_func_case::real_op() { DBUG_ASSERT(fixed == 1); char buff[MAX_FIELD_WIDTH]; @@ -2936,7 +2966,7 @@ double Item_func_case::val_real() } -my_decimal *Item_func_case::val_decimal(my_decimal *decimal_value) +my_decimal *Item_func_case::decimal_op(my_decimal *decimal_value) { DBUG_ASSERT(fixed == 1); char buff[MAX_FIELD_WIDTH]; @@ -2956,6 +2986,18 @@ my_decimal *Item_func_case::val_decimal(my_decimal *decimal_value) } +bool Item_func_case::date_op(MYSQL_TIME *ltime, uint fuzzydate) +{ + DBUG_ASSERT(fixed == 1); + char buff[MAX_FIELD_WIDTH]; + String dummy_str(buff, sizeof(buff), default_charset()); + Item *item= find_item(&dummy_str); + if (!item) + return (null_value= true); + return (null_value= item->get_date(ltime, fuzzydate)); +} + + bool Item_func_case::fix_fields(THD *thd, Item **ref) { /* @@ -3023,7 +3065,10 @@ void Item_func_case::fix_length_and_dec() if (!(agg= (Item**) sql_alloc(sizeof(Item*)*(ncases+1)))) return; - + + if (else_expr_num == -1 || args[else_expr_num]->maybe_null) + maybe_null= 1; + /* Aggregate all THEN and ELSE expression types and collations when string result @@ -3036,9 +3081,11 @@ void Item_func_case::fix_length_and_dec() agg[nagg++]= args[else_expr_num]; agg_result_type(&cached_result_type, agg, nagg); + cached_field_type= agg_field_type(agg, nagg); + if (cached_result_type == STRING_RESULT) { - if (agg_arg_charsets_for_string_result(collation, agg, nagg)) + if (count_string_result_length(cached_field_type, agg, nagg)) return; /* Copy all THEN and ELSE items back to args[] array. @@ -3051,11 +3098,22 @@ void Item_func_case::fix_length_and_dec() change_item_tree_if_needed(thd, &args[else_expr_num], agg[nagg++]); } else + { collation.set_numeric(); + max_length=0; + decimals=0; + unsigned_flag= TRUE; + for (uint i= 0; i < ncases; i+= 2) + agg_num_lengths(args[i + 1]); + if (else_expr_num != -1) + agg_num_lengths(args[else_expr_num]); + max_length= my_decimal_precision_to_length_no_truncation(max_length + + decimals, decimals, + unsigned_flag); + } - cached_field_type= agg_field_type(agg, nagg); /* - Aggregate first expression and all THEN expression types + Aggregate first expression and all WHEN expression types and collations when string comparison */ if (first_expr_num != -1) @@ -3141,30 +3199,6 @@ void Item_func_case::fix_length_and_dec() args[i]->cmp_context= item_cmp_type(left_result_type, args[i]->result_type()); } - - if (else_expr_num == -1 || args[else_expr_num]->maybe_null) - maybe_null=1; - - max_length=0; - decimals=0; - unsigned_flag= TRUE; - if (cached_result_type == STRING_RESULT) - { - for (uint i= 0; i < ncases; i+= 2) - agg_str_lengths(args[i + 1]); - if (else_expr_num != -1) - agg_str_lengths(args[else_expr_num]); - } - else - { - for (uint i= 0; i < ncases; i+= 2) - agg_num_lengths(args[i + 1]); - if (else_expr_num != -1) - agg_num_lengths(args[else_expr_num]); - max_length= my_decimal_precision_to_length_no_truncation(max_length + - decimals, decimals, - unsigned_flag); - } } @@ -3272,18 +3306,18 @@ double Item_func_coalesce::real_op() } -bool Item_func_coalesce::get_date(MYSQL_TIME *ltime,ulonglong fuzzydate) +bool Item_func_coalesce::date_op(MYSQL_TIME *ltime,uint fuzzydate) { DBUG_ASSERT(fixed == 1); null_value= 0; for (uint i= 0; i < arg_count; i++) { - bool res= args[i]->get_date(ltime, fuzzydate); + bool res= args[i]->get_date(ltime, fuzzydate & ~TIME_FUZZY_DATES); if (!args[i]->null_value) return res; } - null_value=1; - return 1; + bzero((char*) ltime,sizeof(*ltime)); + return null_value|= !(fuzzydate & TIME_FUZZY_DATES); } @@ -3305,21 +3339,11 @@ my_decimal *Item_func_coalesce::decimal_op(my_decimal *decimal_value) void Item_func_coalesce::fix_length_and_dec() { cached_field_type= agg_field_type(args, arg_count); - agg_result_type(&hybrid_type, args, arg_count); - Item_result cmp_type; - agg_cmp_type(&cmp_type, args, arg_count); - ///< @todo let result_type() return TIME_RESULT and remove this special case - if (cmp_type == TIME_RESULT) - { - count_real_length(); - return; - } - switch (hybrid_type) { + agg_result_type(&cached_result_type, args, arg_count); + switch (cached_result_type) { case STRING_RESULT: - decimals= NOT_FIXED_DEC; - if (agg_arg_charsets_for_string_result(collation, args, arg_count)) - return; - count_only_length(); + if (count_string_result_length(cached_field_type, args, arg_count)) + return; break; case DECIMAL_RESULT: count_decimal_length(); @@ -3328,7 +3352,7 @@ void Item_func_coalesce::fix_length_and_dec() count_real_length(); break; case INT_RESULT: - count_only_length(); + count_only_length(args, arg_count); decimals= 0; break; case ROW_RESULT: @@ -4341,12 +4365,32 @@ Item_cond::fix_fields(THD *thd, Item **ref) return TRUE; /* purecov: inspected */ used_tables_cache|= item->used_tables(); if (item->const_item()) - and_tables_cache= (table_map) 0; + { + if (!item->is_expensive() && !cond_has_datetime_is_null(item) && + item->val_int() == 0) + { + /* + This is "... OR false_cond OR ..." + In this case, false_cond has no effect on cond_or->not_null_tables() + */ + } + else + { + /* + This is "... OR const_cond OR ..." + In this case, cond_or->not_null_tables()=0, because the condition + const_cond might evaluate to true (regardless of whether some tables + were NULL-complemented). + */ + and_tables_cache= (table_map) 0; + } + } else { table_map tmp_table_map= item->not_null_tables(); not_null_tables_cache|= tmp_table_map; and_tables_cache&= tmp_table_map; + const_item_cache= FALSE; } @@ -4374,7 +4418,26 @@ Item_cond::eval_not_null_tables(uchar *opt_arg) { table_map tmp_table_map; if (item->const_item()) - and_tables_cache= (table_map) 0; + { + if (!item->is_expensive() && !cond_has_datetime_is_null(item) && + item->val_int() == 0) + { + /* + This is "... OR false_cond OR ..." + In this case, false_cond has no effect on cond_or->not_null_tables() + */ + } + else + { + /* + This is "... OR const_cond OR ..." + In this case, cond_or->not_null_tables()=0, because the condition + some_cond_or might be true regardless of what tables are + NULL-complemented. + */ + and_tables_cache= (table_map) 0; + } + } else { tmp_table_map= item->not_null_tables(); @@ -4598,8 +4661,6 @@ void Item_cond::update_used_tables() item->update_used_tables(); used_tables_cache|= item->used_tables(); const_item_cache&= item->const_item(); - if (!persistent_maybe_null && item->maybe_null) - maybe_null= 1; } } @@ -4752,13 +4813,18 @@ Item *and_expressions(Item *a, Item *b, Item **org_item) longlong Item_func_isnull::val_int() { DBUG_ASSERT(fixed == 1); + if (const_item() && !args[0]->maybe_null) + return 0; return args[0]->is_null() ? 1: 0; } + longlong Item_is_not_null_test::val_int() { DBUG_ASSERT(fixed == 1); DBUG_ENTER("Item_is_not_null_test::val_int"); + if (const_item() && !args[0]->maybe_null) + DBUG_RETURN(1); if (args[0]->is_null()) { DBUG_PRINT("info", ("null")); @@ -4774,9 +4840,10 @@ longlong Item_is_not_null_test::val_int() */ void Item_is_not_null_test::update_used_tables() { - args[0]->update_used_tables(); if (!args[0]->maybe_null) used_tables_cache= 0; /* is always true */ + else + args[0]->update_used_tables(); } @@ -4956,6 +5023,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) return FALSE; } + void Item_func_like::cleanup() { canDoTurboBM= FALSE; @@ -4984,156 +5052,193 @@ bool Item_func_like::find_selective_predicates_list_processor(uchar *arg) } + +/** + 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; - if ((error= my_regcomp(&preg, res->c_ptr_safe(), - regex_lib_flags, regex_lib_charset))) + m_pcre= pcre_compile(pattern->c_ptr_safe(), m_library_flags, + &pcreErrorStr, &pcreErrorOffset, NULL); + + 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_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; - /* - If the case of UCS2 and other non-ASCII character sets, - we will convert patterns and strings to UTF8. - */ - regex_lib_charset= (cmp_collation.collation->mbminlen > 1) ? - &my_charset_utf8_general_ci : - cmp_collation.collation; +bool Regexp_processor_pcre::exec(const char *str, int length, int offset) +{ + m_pcre_exec_rc= pcre_exec(m_pcre, NULL, str, length, + offset, 0, m_SubStrVec, m_subpatterns_needed * 3); + return false; +} - 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()) - { - int comp_res= regcomp(TRUE); - if (comp_res == -1) - { // Will always return NULL - set_persist_maybe_null(1); - fixed= 1; - 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(m_pcre, NULL, str->c_ptr_safe(), str->length(), + offset, 0, m_SubStrVec, m_subpatterns_needed * 3); + if (m_pcre_exec_rc > 0) + { + 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 - set_persist_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() +void +Item_func_regex::fix_length_and_dec() { - 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; + Item_bool_func::fix_length_and_dec(); + + if (agg_arg_charsets_for_comparison(cmp_collation, args, 2)) + return; + + re.init(cmp_collation.collation, 0, 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(); +} + + +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, 1); + 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; } @@ -5609,7 +5714,8 @@ Item_equal::Item_equal(Item *f1, Item *f2, bool with_const_item) equal_items.push_back(f1); equal_items.push_back(f2); compare_as_dates= with_const_item && f2->cmp_type() == TIME_RESULT; - upper_levels= NULL; + upper_levels= NULL; + sargable= TRUE; } @@ -5640,6 +5746,7 @@ Item_equal::Item_equal(Item_equal *item_equal) compare_as_dates= item_equal->compare_as_dates; cond_false= item_equal->cond_false; upper_levels= item_equal->upper_levels; + sargable= TRUE; } @@ -5693,6 +5800,12 @@ void Item_equal::add_const(Item *c, Item *f) func->quick_fix_field(); cond_false= !func->val_int(); } + /* + TODO: also support the case where Item_equal becomes singular with + this->is_cond_true()=1. When I attempted to mark the item as constant, + the optimizer attempted to remove it, however it is still referenced from + COND_EQUAL and I got a crash. + */ if (cond_false) const_item_cache= 1; } @@ -5768,10 +5881,12 @@ void Item_equal::merge(Item_equal *item) @brief Merge members of another Item_equal object into this one - @param item multiple equality whose members are to be merged + @param item multiple equality whose members are to be merged + @param save_merged keep the list of equalities in 'item' intact + (e.g. for other merges) @details - If the Item_equal 'item' happened to have some elements of the list + If the Item_equal 'item' happens to have some elements of the list of equal items belonging to 'this' object then the function merges the equal items from 'item' into this list. If both lists contains constants and they are different then @@ -5786,24 +5901,45 @@ void Item_equal::merge(Item_equal *item) The method 'merge' just joins the list of equal items belonging to 'item' to the list of equal items belonging to this object assuming that the lists are disjoint. It would be more correct to call the method 'join'. - The method 'merge_with_check' really merges two lists of equal items if they - have common members. + The method 'merge_into_with_check' really merges two lists of equal items if + they have common members. */ -bool Item_equal::merge_with_check(Item_equal *item) +bool Item_equal::merge_with_check(Item_equal *item, bool save_merged) { bool intersected= FALSE; - Item_equal_fields_iterator_slow fi(*this); + Item_equal_fields_iterator_slow fi(*item); + while (fi++) { - if (item->contains(fi.get_curr_field())) + if (contains(fi.get_curr_field())) { - fi.remove(); intersected= TRUE; + if (!save_merged) + fi.remove(); } } if (intersected) - item->merge(this); + { + if (!save_merged) + merge(item); + else + { + Item *c= item->get_const(); + if (c) + add_const(c); + if (!cond_false) + { + Item *item; + fi.rewind(); + while ((item= fi++)) + { + if (!contains(fi.get_curr_field())) + add(item); + } + } + } + } return intersected; } @@ -5812,17 +5948,25 @@ bool Item_equal::merge_with_check(Item_equal *item) @brief Merge this object into a list of Item_equal objects - @param list the list of Item_equal objects to merge into + @param list the list of Item_equal objects to merge into + @param save_merged keep the list of equalities in 'this' intact + (e.g. for other merges) + @param only_intersected do not merge if there are no common members + in any of Item_equal objects from the list + and this Item_equal @details If the list of equal items from 'this' object contains common members with the lists of equal items belonging to Item_equal objects from 'list' then all involved Item_equal objects e1,...,ek are merged into one - Item equal that replaces e1,...,ek in the 'list'. Otherwise this + Item equal that replaces e1,...,ek in the 'list'. Otherwise, in the case + when the value of the parameter only_if_intersected is false, this Item_equal is joined to the 'list'. */ -void Item_equal::merge_into_list(List<Item_equal> *list) +void Item_equal::merge_into_list(List<Item_equal> *list, + bool save_merged, + bool only_intersected) { Item_equal *item; List_iterator<Item_equal> it(*list); @@ -5831,16 +5975,16 @@ void Item_equal::merge_into_list(List<Item_equal> *list) { if (!merge_into) { - if (merge_with_check(item)) + if (item->merge_with_check(this, save_merged)) merge_into= item; } else { - if (item->merge_with_check(merge_into)) + if (merge_into->merge_with_check(item, false)) it.remove(); } } - if (!merge_into) + if (!only_intersected && !merge_into) list->push_back(this); } @@ -5866,7 +6010,8 @@ void Item_equal::merge_into_list(List<Item_equal> *list) void Item_equal::sort(Item_field_cmpfunc compare, void *arg) { - bubble_sort<Item>(&equal_items, compare, arg); + if (equal_items.elements > 1) + bubble_sort<Item>(&equal_items, compare, arg); } @@ -5994,6 +6139,12 @@ bool Item_equal::fix_fields(THD *thd, Item **ref) void Item_equal::update_used_tables() { not_null_tables_cache= used_tables_cache= 0; + /* + TODO: also support the case where Item_equal becomes singular with + this->is_cond_true()=1. When I attempted to mark the item as constant, + the optimizer attempted to remove it, however it is still referenced from + COND_EQUAL and I got a crash. + */ if ((const_item_cache= cond_false)) return; Item_equal_fields_iterator it(*this); @@ -6005,12 +6156,18 @@ void Item_equal::update_used_tables() used_tables_cache|= item->used_tables(); /* see commentary at Item_equal::update_const() */ const_item_cache&= item->const_item() && !item->is_outer_field(); - if (!persistent_maybe_null && item->maybe_null) - maybe_null= 1; } } +bool Item_equal::count_sargable_conds(uchar *arg) +{ + SELECT_LEX *sel= (SELECT_LEX *) arg; + uint m= equal_items.elements; + sel->cond_count+= m*(m-1); + return 0; +} + /** @brief @@ -6037,6 +6194,8 @@ longlong Item_equal::val_int() { if (cond_false) return 0; + if (is_cond_true()) + return 1; Item *item= get_const(); Item_equal_fields_iterator it(*this); if (!item) @@ -6061,6 +6220,11 @@ longlong Item_equal::val_int() void Item_equal::fix_length_and_dec() { Item *item= get_first(NO_PARTICULAR_TAB, NULL); + if (!item) + { + DBUG_ASSERT(is_cond_true()); // it should be the only constant + item= equal_items.head(); + } eval_item= cmp_item::get_comparator(item->cmp_type(), item, item->collation.collation); } @@ -6326,7 +6490,7 @@ longlong Item_func_dyncol_exists::val_int() /* We do not change the string, so could do this trick */ col.str= (char *)str->ptr(); rc= ((name == NULL) ? - mariadb_dyncol_exists(&col, (uint) num) : + mariadb_dyncol_exists_num(&col, (uint) num) : mariadb_dyncol_exists_named(&col, name)); if (rc < 0) { diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index bf65d6e7c07..a438139bcfe 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -25,7 +25,8 @@ #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); class Item_bool_func2; @@ -371,7 +372,8 @@ protected: public: Item_bool_func2(Item *a,Item *b) - :Item_int_func(a,b), cmp(tmp_arg, tmp_arg+1), abort_on_null(FALSE) {} + :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() { @@ -514,8 +516,8 @@ public: bool fix_fields(THD *thd, Item **ref) {return Item_func::fix_fields(thd, ref);} virtual void print(String *str, enum_query_type query_type); - void set_sum_test(Item_sum_hybrid *item) { test_sum_item= item; }; - void set_sub_test(Item_maxmin_subselect *item) { test_sub_item= item; }; + void set_sum_test(Item_sum_hybrid *item) { test_sum_item= item; test_sub_item= 0; }; + void set_sub_test(Item_maxmin_subselect *item) { test_sub_item= item; test_sum_item= 0;}; bool empty_underlying_subquery(); Item *neg_transformer(THD *thd); }; @@ -676,7 +678,7 @@ public: /* 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) {} + :Item_func_opt_neg(a, b, c), compare_as_dates(FALSE) { sargable= TRUE; } longlong val_int(); optimize_type select_optimize() const { return OPTIMIZE_KEY; } enum Functype functype() const { return BETWEEN; } @@ -689,6 +691,7 @@ public: 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); + bool count_sargable_conds(uchar *arg); }; @@ -737,24 +740,19 @@ public: }; -class Item_func_coalesce :public Item_func_numhybrid +class Item_func_coalesce :public Item_func_hybrid_field_type { -protected: - enum_field_types cached_field_type; - Item_func_coalesce(Item *a, Item *b) :Item_func_numhybrid(a, b) {} public: - Item_func_coalesce(List<Item> &list) :Item_func_numhybrid(list) {} + 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) {} 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 find_num_type() {} - enum Item_result result_type () const { return hybrid_type; } const char *func_name() const { return "coalesce"; } table_map not_null_tables() const { return 0; } - enum_field_types field_type() const { return cached_field_type; } - bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate); }; @@ -768,40 +766,27 @@ public: longlong int_op(); String *str_op(String *str); my_decimal *decimal_op(my_decimal *); - enum_field_types field_type() const; + bool date_op(MYSQL_TIME *ltime,uint fuzzydate); void fix_length_and_dec(); - void update_used_tables() - { - Item_func_coalesce::update_used_tables(); - maybe_null|= args[1]->maybe_null; - } const char *func_name() const { return "ifnull"; } Field *tmp_table_field(TABLE *table); uint decimal_precision() const; }; -class Item_func_if :public Item_func +class Item_func_if :public Item_func_hybrid_field_type { - enum Item_result cached_result_type; - enum_field_types cached_field_type; public: Item_func_if(Item *a,Item *b,Item *c) - :Item_func(a,b,c), cached_result_type(INT_RESULT) + :Item_func_hybrid_field_type(a,b,c) {} - 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; } - enum_field_types field_type() const { return cached_field_type; } + bool date_op(MYSQL_TIME *ltime, uint fuzzydate); + longlong int_op(); + double real_op(); + my_decimal *decimal_op(my_decimal *); + String *str_op(String *); bool fix_fields(THD *, Item **); void fix_length_and_dec(); - void update_used_tables() - { - Item_func::update_used_tables(); - maybe_null|= args[1]->maybe_null || args[2]->maybe_null; - } uint decimal_precision() const; const char *func_name() const { return "if"; } bool eval_not_null_tables(uchar *opt_arg); @@ -1231,21 +1216,20 @@ public: function and only comparators for there result types are used. */ -class Item_func_case :public Item_func +class Item_func_case :public Item_func_hybrid_field_type { int first_expr_num, else_expr_num; - enum Item_result cached_result_type, left_result_type; + enum Item_result left_result_type; String tmp_value; uint ncases; Item_result cmp_type; DTCollation cmp_collation; - enum_field_types cached_field_type; cmp_item *cmp_items[6]; /* For all result types */ cmp_item *case_item; public: Item_func_case(List<Item> &list, Item *first_expr_arg, Item *else_expr_arg) - :Item_func(), first_expr_num(-1), else_expr_num(-1), - cached_result_type(INT_RESULT), left_result_type(INT_RESULT), case_item(0) + :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) @@ -1261,22 +1245,15 @@ public: set_arguments(list); bzero(&cmp_items, sizeof(cmp_items)); } - double val_real(); - longlong val_int(); - String *val_str(String *); - my_decimal *val_decimal(my_decimal *); + double real_op(); + longlong int_op(); + String *str_op(String *); + my_decimal *decimal_op(my_decimal *); + bool date_op(MYSQL_TIME *ltime, uint fuzzydate); bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(); - void update_used_tables() - { - Item_func::update_used_tables(); - if (else_expr_num == -1 || args[else_expr_num]->maybe_null) - maybe_null= 1; - } uint decimal_precision() const; table_map not_null_tables() const { return 0; } - enum Item_result result_type () const { return cached_result_type; } - enum_field_types field_type() const { return cached_field_type; } const char *func_name() const { return "case"; } virtual void print(String *str, enum_query_type query_type); Item *find_item(String *str); @@ -1320,10 +1297,11 @@ public: Item_func_in(List<Item> &list) :Item_func_opt_neg(list), array(0), have_null(0), - arg_types_compatible(FALSE) + 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 **); @@ -1389,19 +1367,18 @@ public: class Item_func_isnull :public Item_bool_func { public: - Item_func_isnull(Item *a) :Item_bool_func(a) {} + Item_func_isnull(Item *a) :Item_bool_func(a) { sargable= TRUE; } longlong val_int(); enum Functype functype() const { return ISNULL_FUNC; } void fix_length_and_dec() { - decimals=0; max_length=1; set_persist_maybe_null(0); + decimals=0; max_length=1; maybe_null=0; update_used_tables(); } const char *func_name() const { return "isnull"; } /* Optimize case of not_null_column IS NULL */ virtual void update_used_tables() { - args[0]->update_used_tables(); if (!args[0]->maybe_null) { used_tables_cache= 0; /* is always false */ @@ -1409,6 +1386,7 @@ public: } else { + args[0]->update_used_tables(); used_tables_cache= args[0]->used_tables(); const_item_cache= args[0]->const_item(); } @@ -1451,12 +1429,13 @@ class Item_func_isnotnull :public Item_bool_func { bool abort_on_null; public: - Item_func_isnotnull(Item *a) :Item_bool_func(a), abort_on_null(0) {} + Item_func_isnotnull(Item *a) :Item_bool_func(a), abort_on_null(0) + { sargable= TRUE; } longlong val_int(); enum Functype functype() const { return ISNOTNULL_FUNC; } void fix_length_and_dec() { - decimals=0; max_length=1; set_persist_maybe_null(0); + decimals=0; max_length=1; maybe_null=0; } const char *func_name() const { return "isnotnull"; } optimize_type select_optimize() const { return OPTIMIZE_NULL; } @@ -1510,29 +1489,102 @@ public: }; +class Regexp_processor_pcre +{ + pcre *m_pcre; + 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]; + uint m_subpatterns_needed; +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_subpatterns_needed(0) + {} + void init(CHARSET_INFO *data_charset, int extra_flags, uint nsubpatterns) + { + m_library_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); + m_subpatterns_needed= nsubpatterns; + } + 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(); - longlong val_int(); - bool fix_fields(THD *thd, Item **ref); - void update_used_tables() + Item_func_regex(Item *a,Item *b) :Item_bool_func(a,b) + {} + void cleanup() { - Item_bool_func::update_used_tables(); - if (regex_is_const) - maybe_null= 1; + DBUG_ENTER("Item_func_regex::cleanup"); + Item_bool_func::cleanup(); + re.cleanup(); + DBUG_VOID_RETURN; } + longlong val_int(); + void fix_length_and_dec(); const char *func_name() const { return "regexp"; } virtual inline void print(String *str, enum_query_type query_type) @@ -1544,6 +1596,26 @@ public: }; +class Item_func_regexp_instr :public Item_int_func +{ + Regexp_processor_pcre re; + DTCollation cmp_collation; +public: + Item_func_regexp_instr(Item *a, Item *b) :Item_int_func(a, b) + {} + void cleanup() + { + DBUG_ENTER("Item_func_regexp_instr::cleanup"); + Item_int_func::cleanup(); + re.cleanup(); + DBUG_VOID_RETURN; + } + longlong val_int(); + void fix_length_and_dec(); + const char *func_name() const { return "regexp_instr"; } +}; + + typedef class Item COND; class Item_cond :public Item_bool_func @@ -1749,11 +1821,12 @@ public: inline Item_equal() : Item_bool_func(), with_const(FALSE), eval_item(0), cond_false(0), context_field(NULL) - { const_item_cache=0 ;} + { const_item_cache=0; sargable= TRUE; } Item_equal(Item *f1, Item *f2, bool with_const_item); Item_equal(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; } + inline bool is_cond_true() { return equal_items.elements == 1; } void add_const(Item *c, Item *f = NULL); /** Add a non-constant item to the multiple equality */ void add(Item *f) { equal_items.push_back(f); } @@ -1762,8 +1835,9 @@ public: /** 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); - void merge_into_list(List<Item_equal> *list); + bool merge_with_check(Item_equal *equal_item, bool save_merged); + void merge_into_list(List<Item_equal> *list, bool save_merged, + bool only_intersected); void update_const(); enum Functype functype() const { return MULT_EQUAL_FUNC; } longlong val_int(); @@ -1781,6 +1855,7 @@ public: 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, diff --git a/sql/item_create.cc b/sql/item_create.cc index 3d0a2f58eb7..fa8c016d61b 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -1151,6 +1151,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: @@ -2015,6 +2028,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: @@ -2357,6 +2409,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: @@ -3812,6 +3877,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(arg1); +} + + Create_func_found_rows Create_func_found_rows::s_singleton; Item* @@ -4707,6 +4782,33 @@ Create_func_quote::create_1_arg(THD *thd, Item *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(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(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(arg1, arg2); +} + + Create_func_radians Create_func_radians::s_singleton; Item* @@ -5040,6 +5142,15 @@ Create_func_timediff::create_2_arg(THD *thd, Item *arg1, Item *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(arg1); +} + + Create_func_to_days Create_func_to_days::s_singleton; Item* @@ -5393,6 +5504,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)}, @@ -5490,6 +5602,9 @@ 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)}, @@ -5576,6 +5691,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)}, diff --git a/sql/item_func.cc b/sql/item_func.cc index b1db207dbaf..a49dbb867d6 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -110,7 +110,7 @@ void Item_func::set_arguments(List<Item> &list) } Item_func::Item_func(List<Item> &list) - :allowed_arg_cols(1), persistent_maybe_null(0) + :allowed_arg_cols(1) { set_arguments(list); } @@ -118,7 +118,6 @@ Item_func::Item_func(List<Item> &list) Item_func::Item_func(THD *thd, Item_func *item) :Item_result_field(thd, item), allowed_arg_cols(item->allowed_arg_cols), - persistent_maybe_null(0), arg_count(item->arg_count), used_tables_cache(item->used_tables_cache), not_null_tables_cache(item->not_null_tables_cache), @@ -446,8 +445,6 @@ void Item_func::update_used_tables() args[i]->update_used_tables(); used_tables_cache|=args[i]->used_tables(); const_item_cache&=args[i]->const_item(); - if (!persistent_maybe_null && args[i]->maybe_null) - maybe_null= 1; } } @@ -618,6 +615,30 @@ void Item_func_numhybrid::fix_num_length_and_dec() {} + +/** + Count max_length and decimals for temporal functions. + + @param item Argument array + @param nitems Number of arguments in the array. + + @retval False on success, true on error. +*/ +void Item_func::count_datetime_length(Item **item, uint nitems) +{ + unsigned_flag= 0; + decimals= 0; + if (field_type() != 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()); + fix_char_length(len); +} + /** Set max_length/decimals of function if function is fixed point and result length/precision depends on argument ones. @@ -645,14 +666,14 @@ void Item_func::count_decimal_length() Set max_length of if it is maximum length of its arguments. */ -void Item_func::count_only_length() +void Item_func::count_only_length(Item **item, uint nitems) { uint32 char_length= 0; unsigned_flag= 0; - for (uint i=0 ; i < arg_count ; i++) + for (uint i= 0; i < nitems ; i++) { - set_if_bigger(char_length, args[i]->max_char_length()); - set_if_bigger(unsigned_flag, args[i]->unsigned_flag); + set_if_bigger(char_length, item[i]->max_char_length()); + set_if_bigger(unsigned_flag, item[i]->unsigned_flag); } fix_char_length(char_length); } @@ -689,6 +710,30 @@ void Item_func::count_real_length() } +/** + Calculate max_length and decimals for STRING_RESULT functions. + + @param field_type Field type. + @param items Argument array. + @param nitems Number of arguments. + + @retval False on success, true on error. +*/ +bool Item_func::count_string_result_length(enum_field_types field_type, + Item **items, uint nitems) +{ + if (agg_arg_charsets(collation, items, nitems, MY_COLL_ALLOW_CONV, 1)) + return true; + if (is_temporal_type(field_type)) + count_datetime_length(items, nitems); + else + { + decimals= NOT_FIXED_DEC; + count_only_length(items, nitems); + } + return false; +} + void Item_func::signal_divide_by_null() { @@ -714,6 +759,16 @@ 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) { @@ -761,26 +816,26 @@ void Item_num_op::find_num_type(void) { count_real_length(); max_length= float_length(decimals); - hybrid_type= REAL_RESULT; + cached_result_type= REAL_RESULT; } else if (r0 == DECIMAL_RESULT || r1 == DECIMAL_RESULT || r0 == TIME_RESULT || r1 == TIME_RESULT) { - hybrid_type= DECIMAL_RESULT; + cached_result_type= DECIMAL_RESULT; result_precision(); fix_decimals(); } else { DBUG_ASSERT(r0 == INT_RESULT && r1 == INT_RESULT); - hybrid_type=INT_RESULT; + cached_result_type=INT_RESULT; result_precision(); decimals= 0; } DBUG_PRINT("info", ("Type: %s", - (hybrid_type == REAL_RESULT ? "REAL_RESULT" : - hybrid_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : - hybrid_type == INT_RESULT ? "INT_RESULT" : + (cached_result_type == REAL_RESULT ? "REAL_RESULT" : + cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : + cached_result_type == INT_RESULT ? "INT_RESULT" : "--ILLEGAL!!!--"))); DBUG_VOID_RETURN; } @@ -796,17 +851,17 @@ void Item_func_num1::find_num_type() { DBUG_ENTER("Item_func_num1::find_num_type"); DBUG_PRINT("info", ("name %s", func_name())); - switch (hybrid_type= args[0]->cast_to_int_type()) { + switch (cached_result_type= args[0]->cast_to_int_type()) { case INT_RESULT: unsigned_flag= args[0]->unsigned_flag; break; case STRING_RESULT: case REAL_RESULT: - hybrid_type= REAL_RESULT; + cached_result_type= REAL_RESULT; max_length= float_length(decimals); break; case TIME_RESULT: - hybrid_type= DECIMAL_RESULT; + cached_result_type= DECIMAL_RESULT; case DECIMAL_RESULT: break; case ROW_RESULT: @@ -814,9 +869,9 @@ void Item_func_num1::find_num_type() DBUG_ASSERT(0); } DBUG_PRINT("info", ("Type: %s", - (hybrid_type == REAL_RESULT ? "REAL_RESULT" : - hybrid_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : - hybrid_type == INT_RESULT ? "INT_RESULT" : + (cached_result_type == REAL_RESULT ? "REAL_RESULT" : + cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : + cached_result_type == INT_RESULT ? "INT_RESULT" : "--ILLEGAL!!!--"))); DBUG_VOID_RETURN; } @@ -836,10 +891,10 @@ void Item_func_numhybrid::fix_length_and_dec() } -String *Item_func_numhybrid::val_str(String *str) +String *Item_func_hybrid_result_type::val_str(String *str) { DBUG_ASSERT(fixed == 1); - switch (hybrid_type) { + switch (cached_result_type) { case DECIMAL_RESULT: { my_decimal decimal_value, *val; @@ -867,6 +922,21 @@ String *Item_func_numhybrid::val_str(String *str) break; } case STRING_RESULT: + if (is_temporal_type(field_type())) + { + MYSQL_TIME ltime; + if (date_op(<ime, + field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0) || + str->alloc(MAX_DATE_STRING_REP_LENGTH)) + { + null_value= 1; + 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); + return str; + } return str_op(&str_value); case TIME_RESULT: case ROW_RESULT: @@ -877,10 +947,10 @@ String *Item_func_numhybrid::val_str(String *str) } -double Item_func_numhybrid::val_real() +double Item_func_hybrid_result_type::val_real() { DBUG_ASSERT(fixed == 1); - switch (hybrid_type) { + switch (cached_result_type) { case DECIMAL_RESULT: { my_decimal decimal_value, *val; @@ -899,6 +969,18 @@ double Item_func_numhybrid::val_real() return real_op(); case STRING_RESULT: { + if (is_temporal_type(field_type())) + { + MYSQL_TIME ltime; + if (date_op(<ime, + field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0 )) + { + null_value= 1; + 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(&str_value); @@ -914,10 +996,10 @@ double Item_func_numhybrid::val_real() } -longlong Item_func_numhybrid::val_int() +longlong Item_func_hybrid_result_type::val_int() { DBUG_ASSERT(fixed == 1); - switch (hybrid_type) { + switch (cached_result_type) { case DECIMAL_RESULT: { my_decimal decimal_value, *val; @@ -933,6 +1015,18 @@ longlong Item_func_numhybrid::val_int() return (longlong) rint(real_op()); case STRING_RESULT: { + if (is_temporal_type(field_type())) + { + MYSQL_TIME ltime; + if (date_op(<ime, + field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0)) + { + null_value= 1; + 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(&str_value))) @@ -951,11 +1045,11 @@ longlong Item_func_numhybrid::val_int() } -my_decimal *Item_func_numhybrid::val_decimal(my_decimal *decimal_value) +my_decimal *Item_func_hybrid_result_type::val_decimal(my_decimal *decimal_value) { my_decimal *val= decimal_value; DBUG_ASSERT(fixed == 1); - switch (hybrid_type) { + switch (cached_result_type) { case DECIMAL_RESULT: val= decimal_op(decimal_value); break; @@ -973,6 +1067,19 @@ my_decimal *Item_func_numhybrid::val_decimal(my_decimal *decimal_value) } case STRING_RESULT: { + if (is_temporal_type(field_type())) + { + MYSQL_TIME ltime; + if (date_op(<ime, + field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0)) + { + my_decimal_set_zero(decimal_value); + null_value= 1; + return 0; + } + ltime.time_type= mysql_type_to_time_type(field_type()); + return date2my_decimal(<ime, decimal_value); + } String *res; if (!(res= str_op(&str_value))) return NULL; @@ -990,6 +1097,63 @@ my_decimal *Item_func_numhybrid::val_decimal(my_decimal *decimal_value) } +bool Item_func_hybrid_result_type::get_date(MYSQL_TIME *ltime, + ulonglong fuzzydate) +{ + DBUG_ASSERT(fixed == 1); + switch (cached_result_type) { + case DECIMAL_RESULT: + { + my_decimal value, *res; + if (!(res= decimal_op(&value)) || + decimal_to_datetime_with_warn(res, ltime, fuzzydate, + field_name_or_null())) + goto err; + break; + } + case INT_RESULT: + { + longlong value= int_op(); + if (null_value || int_to_datetime_with_warn(value, ltime, fuzzydate, + field_name_or_null())) + goto err; + break; + } + case REAL_RESULT: + { + double value= real_op(); + if (null_value || double_to_datetime_with_warn(value, ltime, fuzzydate, + field_name_or_null())) + goto err; + break; + } + 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(&tmp)) || + str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(), + ltime, fuzzydate) <= MYSQL_TIMESTAMP_ERROR) + goto err; + break; + break; + } + case ROW_RESULT: + case TIME_RESULT: + case IMPOSSIBLE_RESULT: + DBUG_ASSERT(0); + } + + return (null_value= 0); + +err: + bzero(ltime, sizeof(*ltime)); + return null_value|= !(fuzzydate & TIME_FUZZY_DATES); +} + + void Item_func_signed::print(String *str, enum_query_type query_type) { str->append(STRING_WITH_LEN("cast(")); @@ -1684,7 +1848,7 @@ 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 (hybrid_type) { + switch (cached_result_type) { case REAL_RESULT: { decimals=MY_MAX(args[0]->decimals,args[1]->decimals)+prec_increment; @@ -1700,7 +1864,7 @@ void Item_func_div::fix_length_and_dec() break; } case INT_RESULT: - hybrid_type= DECIMAL_RESULT; + cached_result_type= DECIMAL_RESULT; DBUG_PRINT("info", ("Type changed: DECIMAL_RESULT")); result_precision(); break; @@ -1714,7 +1878,7 @@ void Item_func_div::fix_length_and_dec() case IMPOSSIBLE_RESULT: DBUG_ASSERT(0); } - set_persist_maybe_null(1); // devision by zero + maybe_null= 1; // devision by zero DBUG_VOID_RETURN; } @@ -1798,7 +1962,7 @@ void Item_func_int_div::fix_length_and_dec() max_length=args[0]->max_length - (argtype == DECIMAL_RESULT || argtype == INT_RESULT ? args[0]->decimals : 0); - set_persist_maybe_null(1); + maybe_null=1; unsigned_flag=args[0]->unsigned_flag | args[1]->unsigned_flag; } @@ -1885,7 +2049,7 @@ void Item_func_mod::result_precision() void Item_func_mod::fix_length_and_dec() { Item_num_op::fix_length_and_dec(); - set_persist_maybe_null(1); + maybe_null= 1; unsigned_flag= args[0]->unsigned_flag; } @@ -1952,7 +2116,7 @@ void Item_func_neg::fix_length_and_dec() 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. */ - if (hybrid_type == INT_RESULT && args[0]->const_item()) + if (cached_result_type == INT_RESULT && args[0]->const_item()) { longlong val= args[0]->val_int(); if ((ulonglong) val >= (ulonglong) LONGLONG_MIN && @@ -1963,7 +2127,7 @@ void Item_func_neg::fix_length_and_dec() Ensure that result is converted to DECIMAL, as longlong can't hold the negated number */ - hybrid_type= DECIMAL_RESULT; + cached_result_type= DECIMAL_RESULT; DBUG_PRINT("info", ("Type changed: DECIMAL_RESULT")); } } @@ -2267,11 +2431,11 @@ void Item_func_int_val::find_num_type() { DBUG_ENTER("Item_func_int_val::find_num_type"); DBUG_PRINT("info", ("name %s", func_name())); - switch (hybrid_type= args[0]->cast_to_int_type()) + switch (cached_result_type= args[0]->cast_to_int_type()) { case STRING_RESULT: case REAL_RESULT: - hybrid_type= REAL_RESULT; + cached_result_type= REAL_RESULT; max_length= float_length(decimals); break; case INT_RESULT: @@ -2284,12 +2448,12 @@ void Item_func_int_val::find_num_type() if ((args[0]->max_length - args[0]->decimals) >= (DECIMAL_LONGLONG_DIGITS - 2)) { - hybrid_type= DECIMAL_RESULT; + cached_result_type= DECIMAL_RESULT; } else { unsigned_flag= args[0]->unsigned_flag; - hybrid_type= INT_RESULT; + cached_result_type= INT_RESULT; } break; case ROW_RESULT: @@ -2297,9 +2461,9 @@ void Item_func_int_val::find_num_type() DBUG_ASSERT(0); } DBUG_PRINT("info", ("Type: %s", - (hybrid_type == REAL_RESULT ? "REAL_RESULT" : - hybrid_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : - hybrid_type == INT_RESULT ? "INT_RESULT" : + (cached_result_type == REAL_RESULT ? "REAL_RESULT" : + cached_result_type == DECIMAL_RESULT ? "DECIMAL_RESULT" : + cached_result_type == INT_RESULT ? "INT_RESULT" : "--ILLEGAL!!!--"))); DBUG_VOID_RETURN; @@ -2414,10 +2578,10 @@ void Item_func_round::fix_length_and_dec() if (args[0]->result_type() == DECIMAL_RESULT) { max_length++; - hybrid_type= DECIMAL_RESULT; + cached_result_type= DECIMAL_RESULT; } else - hybrid_type= REAL_RESULT; + cached_result_type= REAL_RESULT; return; } @@ -2435,14 +2599,14 @@ void Item_func_round::fix_length_and_dec() { decimals= MY_MIN(decimals_to_set, NOT_FIXED_DEC); max_length= float_length(decimals); - hybrid_type= REAL_RESULT; + cached_result_type= REAL_RESULT; return; } switch (args[0]->result_type()) { case REAL_RESULT: case STRING_RESULT: - hybrid_type= REAL_RESULT; + cached_result_type= REAL_RESULT; decimals= MY_MIN(decimals_to_set, NOT_FIXED_DEC); max_length= float_length(decimals); break; @@ -2452,14 +2616,14 @@ void Item_func_round::fix_length_and_dec() int length_can_increase= test(!truncate && (val1 < 0) && !val1_unsigned); max_length= args[0]->max_length + length_can_increase; /* Here we can keep INT_RESULT */ - hybrid_type= INT_RESULT; + cached_result_type= INT_RESULT; decimals= 0; break; } /* fall through */ case DECIMAL_RESULT: { - hybrid_type= DECIMAL_RESULT; + cached_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(); @@ -2799,6 +2963,13 @@ bool Item_func_min_max::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) ltime->time_type= MYSQL_TIMESTAMP_DATE; ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0; } + else if (compare_as_dates->field_type() == MYSQL_TYPE_TIME) + { + ltime->time_type= MYSQL_TIMESTAMP_TIME; + ltime->hour+= (ltime->month * 32 + ltime->day) * 24; + ltime->month= ltime->day= 0; + } + return (null_value= 0); } @@ -3108,7 +3279,7 @@ longlong Item_func_field::val_int() void Item_func_field::fix_length_and_dec() { - set_persist_maybe_null(0); max_length=3; + maybe_null=0; max_length=3; cmp_type= args[0]->result_type(); for (uint i=1; i < arg_count ; i++) cmp_type= item_cmp_type(cmp_type, args[i]->result_type()); @@ -4075,9 +4246,7 @@ longlong Item_func_get_lock::val_int() if (!ull_name_ok(res)) DBUG_RETURN(0); - - DBUG_PRINT("info", ("lock %.*s, thd=%ld", res->length(), res->ptr(), - (long) thd->real_id)); + 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, @@ -4098,6 +4267,7 @@ longlong Item_func_get_lock::val_int() /* Recursive lock */ ull->refs++; null_value = 0; + DBUG_PRINT("info", ("recursive lock, ref-count: %d", (int) ull->refs)); DBUG_RETURN(1); } @@ -4154,7 +4324,7 @@ longlong Item_func_release_lock::val_int() if (!ull_name_ok(res)) DBUG_RETURN(0); - DBUG_PRINT("info", ("lock %.*s", res->length(), res->ptr())); + 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(), ""); @@ -4168,6 +4338,7 @@ longlong Item_func_release_lock::val_int() null_value= thd->mdl_context.get_lock_owner(&ull_key) == 0; DBUG_RETURN(0); } + DBUG_PRINT("info", ("ref count: %d", (int) ull->refs)); null_value= 0; if (--ull->refs == 0) { @@ -5418,7 +5589,7 @@ void Item_func_get_user_var::fix_length_and_dec() { THD *thd=current_thd; int error; - set_persist_maybe_null(1); + maybe_null=1; decimals=NOT_FIXED_DEC; max_length=MAX_BLOB_WIDTH; @@ -5617,7 +5788,7 @@ void Item_func_get_system_var::update_null_value() void Item_func_get_system_var::fix_length_and_dec() { char *cptr; - set_persist_maybe_null(1); + maybe_null= TRUE; max_length= 0; if (var->check_type(var_type)) @@ -6037,7 +6208,7 @@ bool Item_func_match::fix_fields(THD *thd, Item **ref) status_var_increment(thd->status_var.feature_fulltext); - set_persist_maybe_null(1); + maybe_null=1; join_key=0; /* @@ -6323,7 +6494,7 @@ 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) { - set_persist_maybe_null(1); + maybe_null= 1; m_name->init_qname(current_thd); dummy_table= (TABLE*) sql_calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE)); dummy_table->s= (TABLE_SHARE*) (dummy_table+1); @@ -6334,7 +6505,7 @@ 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) { - set_persist_maybe_null(1); + maybe_null= 1; m_name->init_qname(current_thd); dummy_table= (TABLE*) sql_calloc(sizeof(TABLE)+ sizeof(TABLE_SHARE)); dummy_table->s= (TABLE_SHARE*) (dummy_table+1); @@ -6488,7 +6659,7 @@ void Item_func_sp::fix_length_and_dec() decimals= sp_result_field->decimals(); max_length= sp_result_field->field_length; collation.set(sp_result_field->charset()); - set_persist_maybe_null(1); + maybe_null= 1; unsigned_flag= test(sp_result_field->flags & UNSIGNED_FLAG); DBUG_VOID_RETURN; diff --git a/sql/item_func.h b/sql/item_func.h index 68a64fef3cb..03e67ddf11a 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -39,12 +39,17 @@ protected: 0 means get this number from first argument */ uint allowed_arg_cols; - /* maybe_null can't be changed by parameters or used table state */ - bool persistent_maybe_null; String *val_str_from_val_str_ascii(String *str, String *str2); public: uint arg_count; - table_map used_tables_cache, not_null_tables_cache; + /* + 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; + table_map not_null_tables_cache; + bool const_item_cache; enum Functype { UNKNOWN_FUNC,EQ_FUNC,EQUAL_FUNC,NE_FUNC,LT_FUNC,LE_FUNC, GE_FUNC,GT_FUNC,FT_FUNC, @@ -67,13 +72,13 @@ public: enum Type type() const { return FUNC_ITEM; } virtual enum Functype functype() const { return UNKNOWN_FUNC; } Item_func(void): - allowed_arg_cols(1), persistent_maybe_null(0), arg_count(0) + allowed_arg_cols(1), arg_count(0) { with_sum_func= 0; with_field= 0; } Item_func(Item *a): - allowed_arg_cols(1), persistent_maybe_null(0), arg_count(1) + allowed_arg_cols(1), arg_count(1) { args= tmp_arg; args[0]= a; @@ -81,7 +86,7 @@ public: with_field= a->with_field; } Item_func(Item *a,Item *b): - allowed_arg_cols(1), persistent_maybe_null(0), arg_count(2) + allowed_arg_cols(1), arg_count(2) { args= tmp_arg; args[0]= a; args[1]= b; @@ -89,7 +94,7 @@ public: with_field= a->with_field || b->with_field; } Item_func(Item *a,Item *b,Item *c): - allowed_arg_cols(1), persistent_maybe_null(0) + allowed_arg_cols(1) { arg_count= 0; if ((args= (Item**) sql_alloc(sizeof(Item*)*3))) @@ -101,7 +106,7 @@ public: } } Item_func(Item *a,Item *b,Item *c,Item *d): - allowed_arg_cols(1), persistent_maybe_null(0) + allowed_arg_cols(1) { arg_count= 0; if ((args= (Item**) sql_alloc(sizeof(Item*)*4))) @@ -115,7 +120,7 @@ public: } } Item_func(Item *a,Item *b,Item *c,Item *d,Item* e): - allowed_arg_cols(1), persistent_maybe_null(0) + allowed_arg_cols(1) { arg_count= 5; if ((args= (Item**) sql_alloc(sizeof(Item*)*5))) @@ -150,13 +155,16 @@ public: void print_op(String *str, enum_query_type query_type); void print_args(String *str, uint from, enum_query_type query_type); virtual void fix_num_length_and_dec(); - void count_only_length(); + void count_only_length(Item **item, uint nitems); void count_real_length(); void count_decimal_length(); inline bool get_arg0_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { return (null_value=args[0]->get_date(ltime, fuzzy_date)); } + void count_datetime_length(Item **item, uint nitems); + bool count_string_result_length(enum_field_types field_type, + Item **item, uint nitems); inline bool get_arg0_time(MYSQL_TIME *ltime) { return (null_value=args[0]->get_time(ltime)); @@ -180,7 +188,7 @@ public: if (max_result_length >= MAX_BLOB_WIDTH) { max_length= MAX_BLOB_WIDTH; - set_persist_maybe_null(1); + maybe_null= 1; } else max_length= (uint32) max_result_length; @@ -386,11 +394,6 @@ public: info.bool_function= &Item::restore_to_before_no_rows_in_result; walk(&Item::call_bool_func_processor, FALSE, (uchar*) &info); } - inline void set_persist_maybe_null(bool mb_null) - { - maybe_null= mb_null; - persistent_maybe_null= 1; - } }; @@ -411,38 +414,33 @@ public: }; -class Item_func_numhybrid: public Item_func +class Item_func_hybrid_result_type: public Item_func { protected: - Item_result hybrid_type; + Item_result cached_result_type; + public: - Item_func_numhybrid() :Item_func(), hybrid_type(REAL_RESULT) - {} - Item_func_numhybrid(Item *a) :Item_func(a), hybrid_type(REAL_RESULT) + Item_func_hybrid_result_type() :Item_func(), cached_result_type(REAL_RESULT) { collation.set_numeric(); } - Item_func_numhybrid(Item *a,Item *b) - :Item_func(a,b), hybrid_type(REAL_RESULT) + Item_func_hybrid_result_type(Item *a) :Item_func(a), cached_result_type(REAL_RESULT) { collation.set_numeric(); } - Item_func_numhybrid(List<Item> &list) - :Item_func(list), hybrid_type(REAL_RESULT) + Item_func_hybrid_result_type(Item *a,Item *b) + :Item_func(a,b), cached_result_type(REAL_RESULT) + { collation.set_numeric(); } + Item_func_hybrid_result_type(Item *a,Item *b,Item *c) + :Item_func(a,b,c), cached_result_type(REAL_RESULT) + { collation.set_numeric(); } + Item_func_hybrid_result_type(List<Item> &list) + :Item_func(list), cached_result_type(REAL_RESULT) { collation.set_numeric(); } - enum Item_result result_type () const { return hybrid_type; } - void fix_length_and_dec(); - void fix_num_length_and_dec(); - virtual void find_num_type()= 0; /* To be called from fix_length_and_dec */ - - inline void fix_decimals() - { - DBUG_ASSERT(result_type() == DECIMAL_RESULT); - if (decimals == NOT_FIXED_DEC) - set_if_smaller(decimals, max_length - 1); - } + enum Item_result result_type () const { return cached_result_type; } double val_real(); longlong val_int(); my_decimal *val_decimal(my_decimal *); String *val_str(String*str); + bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); /** @brief Performs the operation that this functions implements when the @@ -479,9 +477,75 @@ public: @return The result of the operation. */ virtual String *str_op(String *)= 0; - bool is_null() { update_null_value(); return null_value; } + + /** + @brief Performs the operation that this functions implements when + field type is a temporal type. + @return The result of the operation. + */ + virtual bool date_op(MYSQL_TIME *res, uint fuzzy_date)= 0; + }; + + +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 +{ +protected: + + inline void fix_decimals() + { + DBUG_ASSERT(result_type() == DECIMAL_RESULT); + if (decimals == NOT_FIXED_DEC) + set_if_smaller(decimals, max_length - 1); + } + +public: + Item_func_numhybrid() :Item_func_hybrid_result_type() + { } + Item_func_numhybrid(Item *a) :Item_func_hybrid_result_type(a) + { } + Item_func_numhybrid(Item *a,Item *b) + :Item_func_hybrid_result_type(a,b) + { } + Item_func_numhybrid(Item *a,Item *b,Item *c) + :Item_func_hybrid_result_type(a,b,c) + { } + Item_func_numhybrid(List<Item> &list) + :Item_func_hybrid_result_type(list) + { } + void fix_length_and_dec(); + void fix_num_length_and_dec(); + virtual void find_num_type()= 0; /* To be called from fix_length_and_dec */ + String *str_op(String *str) { DBUG_ASSERT(0); return 0; } + bool date_op(MYSQL_TIME *ltime, uint fuzzydate) { DBUG_ASSERT(0); return true; } +}; + + /* function where type of result detected by first argument */ class Item_func_num1: public Item_func_numhybrid { @@ -491,7 +555,6 @@ public: void fix_num_length_and_dec(); void find_num_type(); - String *str_op(String *str) { DBUG_ASSERT(0); return 0; } }; @@ -508,31 +571,33 @@ class Item_num_op :public Item_func_numhybrid } void find_num_type(); - String *str_op(String *str) { DBUG_ASSERT(0); return 0; } }; class Item_int_func :public Item_func { +protected: + bool sargable; public: Item_int_func() :Item_func() - { collation.set_numeric(); fix_char_length(21); } + { collation.set_numeric(); fix_char_length(21); sargable= false; } Item_int_func(Item *a) :Item_func(a) - { collation.set_numeric(); fix_char_length(21); } + { 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); } + { 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); } + { collation.set_numeric(); fix_char_length(21); sargable= false; } Item_int_func(Item *a,Item *b,Item *c, Item *d) :Item_func(a,b,c,d) - { collation.set_numeric(); fix_char_length(21); } + { 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); } + { collation.set_numeric(); fix_char_length(21); sargable= false; } Item_int_func(THD *thd, Item_int_func *item) :Item_func(thd, item) - { collation.set_numeric(); } + { collation.set_numeric(); sargable= false; } 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); }; @@ -616,7 +681,7 @@ public: } double val_real(); enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; } - void fix_length_and_dec() { set_persist_maybe_null(1); } + void fix_length_and_dec() { maybe_null= 1; } const char *func_name() const { return "double_typecast"; } virtual void print(String *str, enum_query_type query_type); }; @@ -757,7 +822,7 @@ class Item_dec_func :public Item_real_func void fix_length_and_dec() { decimals=NOT_FIXED_DEC; max_length=float_length(decimals); - set_persist_maybe_null(1); + maybe_null=1; } }; @@ -1089,7 +1154,7 @@ public: Item_func_coercibility(Item *a) :Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "coercibility"; } - void fix_length_and_dec() { max_length=10; set_persist_maybe_null(0); } + void fix_length_and_dec() { max_length=10; maybe_null= 0; } table_map not_null_tables() const { return 0; } }; @@ -1252,7 +1317,7 @@ public: {} longlong val_int(); const char *func_name() const { return "benchmark"; } - void fix_length_and_dec() { max_length=1; set_persist_maybe_null(0); } + void fix_length_and_dec() { max_length=1; maybe_null=0; } virtual void print(String *str, enum_query_type query_type); bool check_vcol_func_processor(uchar *int_arg) { @@ -1270,11 +1335,11 @@ public: Item_func_sleep(Item *a) :Item_int_func(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 Item_int_func::used_tables() | RAND_TABLE_BIT; } + bool is_expensive() { return 1; } longlong val_int(); bool check_vcol_func_processor(uchar *int_arg) { @@ -1508,7 +1573,7 @@ public: double val_real() { DBUG_ASSERT(fixed == 1); null_value= 1; return 0.0; } longlong val_int() { DBUG_ASSERT(fixed == 1); null_value=1; return 0; } enum Item_result result_type () const { return STRING_RESULT; } - void fix_length_and_dec() { set_persist_maybe_null(1); max_length=0; } + void fix_length_and_dec() { maybe_null=1; max_length=0; } }; #endif /* HAVE_DLOPEN */ @@ -1523,7 +1588,13 @@ class Item_func_get_lock :public Item_int_func Item_func_get_lock(Item *a,Item *b) :Item_int_func(a,b) {} longlong val_int(); const char *func_name() const { return "get_lock"; } - void fix_length_and_dec() { max_length=1; set_persist_maybe_null(1);} + void fix_length_and_dec() { max_length=1; maybe_null=1;} + table_map used_tables() const + { + return Item_int_func::used_tables() | 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()); @@ -1537,7 +1608,13 @@ public: Item_func_release_lock(Item *a) :Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "release_lock"; } - void fix_length_and_dec() { max_length=1; set_persist_maybe_null(1);} + void fix_length_and_dec() { max_length= 1; maybe_null= 1;} + table_map used_tables() const + { + return Item_int_func::used_tables() | 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()); @@ -1555,7 +1632,7 @@ public: Item_master_pos_wait(Item *a,Item *b, Item *c, Item *d) :Item_int_func(a,b,c,d) {} longlong val_int(); const char *func_name() const { return "master_pos_wait"; } - void fix_length_and_dec() { max_length=21; set_persist_maybe_null(1);} + void fix_length_and_dec() { max_length=21; maybe_null=1;} bool check_vcol_func_processor(uchar *int_arg) { return trace_unsupported_by_check_vcol_func_processor(func_name()); @@ -1600,15 +1677,14 @@ public: :Item_func(b), cached_result_type(INT_RESULT), entry(NULL), entry_thread_id(0), name(a) {} - Item_func_set_user_var(Item_func_set_user_var *item) - :Item_func(item), cached_result_type(item->cached_result_type), - entry(item->entry), 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) - { - //fixed= 1; - } + 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), + value(item->value), decimal_buff(item->decimal_buff), + null_item(item->null_item), save_result(item->save_result), + name(item->name) + {} + enum Functype functype() const { return SUSERVAR_FUNC; } double val_real(); longlong val_int(); @@ -1630,6 +1706,12 @@ public: enum Item_result result_type () const { return cached_result_type; } bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec(); + table_map used_tables() const + { + return Item_func::used_tables() | RAND_TABLE_BIT; + } + bool const_item() const { return 0; } + bool is_expensive() { return 1; } 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"; } @@ -1749,6 +1831,8 @@ public: double val_real(); longlong val_int(); String* val_str(String*); + my_decimal *val_decimal(my_decimal *dec_buf) + { return val_decimal_from_real(dec_buf); } /* TODO: fix to support views */ const char *func_name() const { return "get_system_var"; } /** @@ -1772,8 +1856,7 @@ 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; set_persist_maybe_null(1); unsigned_flag= 1; } + void fix_length_and_dec() { decimals= 0; max_length= 21; maybe_null= 1; unsigned_flag= 1;} }; @@ -1841,8 +1924,7 @@ public: Item_func_is_free_lock(Item *a) :Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "is_free_lock"; } - void fix_length_and_dec() - { decimals= 0; max_length= 1; set_persist_maybe_null(1); } + void fix_length_and_dec() { decimals=0; max_length=1; maybe_null=1;} bool check_vcol_func_processor(uchar *int_arg) { return trace_unsupported_by_check_vcol_func_processor(func_name()); @@ -1856,8 +1938,7 @@ public: Item_func_is_used_lock(Item *a) :Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "is_used_lock"; } - void fix_length_and_dec() - { decimals= 0; max_length= 10; set_persist_maybe_null(1);} + void fix_length_and_dec() { decimals=0; max_length=10; maybe_null=1;} bool check_vcol_func_processor(uchar *int_arg) { return trace_unsupported_by_check_vcol_func_processor(func_name()); @@ -1880,7 +1961,7 @@ public: Item_func_row_count() :Item_int_func() {} longlong val_int(); const char *func_name() const { return "row_count"; } - void fix_length_and_dec() { decimals= 0; set_persist_maybe_null(0); } + void fix_length_and_dec() { decimals= 0; maybe_null=0; } bool check_vcol_func_processor(uchar *int_arg) { @@ -2021,7 +2102,7 @@ public: Item_func_found_rows() :Item_int_func() {} longlong val_int(); const char *func_name() const { return "found_rows"; } - void fix_length_and_dec() { decimals= 0; set_persist_maybe_null(0); } + void fix_length_and_dec() { decimals= 0; maybe_null=0; } bool check_vcol_func_processor(uchar *int_arg) { return trace_unsupported_by_check_vcol_func_processor(func_name()); diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc index b36375a6e40..665c941414c 100644 --- a/sql/item_geofunc.cc +++ b/sql/item_geofunc.cc @@ -53,7 +53,7 @@ void Item_geometry_func::fix_length_and_dec() collation.set(&my_charset_bin); decimals=0; max_length= (uint32) 4294967295U; - set_persist_maybe_null(1); + maybe_null= 1; } @@ -147,7 +147,7 @@ void Item_func_as_wkt::fix_length_and_dec() { collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII); max_length=MAX_BLOB_WIDTH; - set_persist_maybe_null(1); + maybe_null= 1; } diff --git a/sql/item_geofunc.h b/sql/item_geofunc.h index 4d5911324ac..2d715dc8765 100644 --- a/sql/item_geofunc.h +++ b/sql/item_geofunc.h @@ -89,7 +89,7 @@ public: { // "GeometryCollection" is the longest fix_length_and_charset(20, default_charset()); - set_persist_maybe_null(1); + maybe_null= 1; }; }; @@ -224,7 +224,7 @@ public: { Item_func::print(str, query_type); } - void fix_length_and_dec() { set_persist_maybe_null(1); } + void fix_length_and_dec() { maybe_null= 1; } bool is_null() { (void) val_int(); return null_value; } }; @@ -251,7 +251,7 @@ public: Item_func::print(str, query_type); } - void fix_length_and_dec() { set_persist_maybe_null(1); } + void fix_length_and_dec() { maybe_null= 1; } bool is_null() { (void) val_int(); return null_value; } }; @@ -342,7 +342,7 @@ public: longlong val_int(); optimize_type select_optimize() const { return OPTIMIZE_NONE; } const char *func_name() const { return "st_isempty"; } - void fix_length_and_dec() { set_persist_maybe_null(1); } + void fix_length_and_dec() { maybe_null= 1; } }; class Item_func_issimple: public Item_bool_func @@ -356,7 +356,7 @@ public: longlong val_int(); optimize_type select_optimize() const { return OPTIMIZE_NONE; } const char *func_name() const { return "st_issimple"; } - void fix_length_and_dec() { set_persist_maybe_null(1); } + void fix_length_and_dec() { maybe_null= 1; } }; class Item_func_isclosed: public Item_bool_func @@ -366,7 +366,7 @@ public: longlong val_int(); optimize_type select_optimize() const { return OPTIMIZE_NONE; } const char *func_name() const { return "st_isclosed"; } - void fix_length_and_dec() { set_persist_maybe_null(1); } + void fix_length_and_dec() { maybe_null= 1; } }; class Item_func_dimension: public Item_int_func @@ -376,7 +376,7 @@ public: Item_func_dimension(Item *a): Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "st_dimension"; } - void fix_length_and_dec() { max_length= 10; set_persist_maybe_null(1); } + void fix_length_and_dec() { max_length= 10; maybe_null= 1; } }; class Item_func_x: public Item_real_func @@ -389,7 +389,7 @@ public: void fix_length_and_dec() { Item_real_func::fix_length_and_dec(); - set_persist_maybe_null(1); + maybe_null= 1; } }; @@ -404,7 +404,7 @@ public: void fix_length_and_dec() { Item_real_func::fix_length_and_dec(); - set_persist_maybe_null(1); + maybe_null= 1; } }; @@ -416,7 +416,7 @@ public: Item_func_numgeometries(Item *a): Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "st_numgeometries"; } - void fix_length_and_dec() { max_length= 10; set_persist_maybe_null(1); } + void fix_length_and_dec() { max_length= 10; maybe_null= 1; } }; @@ -427,7 +427,7 @@ public: Item_func_numinteriorring(Item *a): Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "st_numinteriorrings"; } - void fix_length_and_dec() { max_length= 10; set_persist_maybe_null(1); } + void fix_length_and_dec() { max_length= 10; maybe_null= 1; } }; @@ -438,7 +438,7 @@ public: Item_func_numpoints(Item *a): Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "st_numpoints"; } - void fix_length_and_dec() { max_length= 10; set_persist_maybe_null(1); } + void fix_length_and_dec() { max_length= 10; maybe_null= 1; } }; @@ -452,7 +452,7 @@ public: void fix_length_and_dec() { Item_real_func::fix_length_and_dec(); - set_persist_maybe_null(1); + maybe_null= 1; } }; @@ -467,7 +467,7 @@ public: void fix_length_and_dec() { Item_real_func::fix_length_and_dec(); - set_persist_maybe_null(1); + maybe_null= 1; } }; @@ -479,7 +479,7 @@ public: Item_func_srid(Item *a): Item_int_func(a) {} longlong val_int(); const char *func_name() const { return "srid"; } - void fix_length_and_dec() { max_length= 10; set_persist_maybe_null(1); } + void fix_length_and_dec() { max_length= 10; maybe_null= 1; } }; diff --git a/sql/item_row.cc b/sql/item_row.cc index 03b460e3ada..6345eaa864b 100644 --- a/sql/item_row.cc +++ b/sql/item_row.cc @@ -146,13 +146,11 @@ void Item_row::update_used_tables() { used_tables_cache= 0; const_item_cache= 1; - maybe_null= 0; 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(); - maybe_null|= items[i]->maybe_null; } } diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index cf8ce614b6d..ef189763d88 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -51,6 +51,7 @@ #include "password.h" // my_make_scrambled_password, // my_make_scrambled_password_323 #include <m_ctype.h> +#include <base64.h> #include <my_md5.h> #include "sha1.h" #include "my_aes.h" @@ -62,7 +63,7 @@ C_MODE_END #include <sql_repl.h> #include "sql_statistics.h" -size_t username_char_length= 16; +size_t username_char_length= 80; /** @todo Remove this. It is not safe to use a shared String object. @@ -322,7 +323,7 @@ String *Item_func_sha2::val_str_ascii(String *str) void Item_func_sha2::fix_length_and_dec() { - set_persist_maybe_null(1); + maybe_null= 1; max_length = 0; #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) @@ -448,9 +449,110 @@ String *Item_func_aes_decrypt::val_str(String *str) void Item_func_aes_decrypt::fix_length_and_dec() { max_length=args[0]->max_length; - set_persist_maybe_null(1); + maybe_null= 1; +} + + +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()) + { + 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(str); + 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)) || + tmp_value.alloc((uint) length)) + { + null_value= 1; // NULL input, too long input, or OOM. + if (too_long) + { + push_warning_printf(current_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); + } + return 0; + } + base64_encode(res->ptr(), (int) res->length(), (char*) tmp_value.ptr()); + DBUG_ASSERT(length > 0); + tmp_value.length((uint) length - 1); // Without trailing '\0' + null_value= 0; + return &tmp_value; +} + + +void Item_func_from_base64::fix_length_and_dec() +{ + 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(str); + bool too_long= false; + int length; + const char *end_ptr; + + if (!res || + res->length() > (uint) base64_decode_max_arg_length() || + (too_long= + ((uint) (length= base64_needed_decoded_length((int) res->length())) > + current_thd->variables.max_allowed_packet)) || + tmp_value.alloc((uint) length) || + (length= base64_decode(res->ptr(), (int) res->length(), + (char *) tmp_value.ptr(), &end_ptr, 0)) < 0 || + end_ptr < res->ptr() + res->length()) + { + null_value= 1; // NULL input, too long input, OOM, or badly formed input + if (too_long) + { + push_warning_printf(current_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); + } + else if (res && length < 0) + { + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_BAD_BASE64_DATA, ER(ER_BAD_BASE64_DATA), + end_ptr - res->ptr()); + } + return 0; + } + tmp_value.length((uint) length); + null_value= 0; + return &tmp_value; +} /////////////////////////////////////////////////////////////////////////////// @@ -1236,6 +1338,187 @@ void Item_func_replace::fix_length_and_dec() } +/*********************************************************************/ +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, 10); + 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 && 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; +} + + +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, 10); + 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); @@ -2232,16 +2515,32 @@ 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(); + return 0; + } + null_value= maybe_null= 1; + return 0; +} + void Item_func_soundex::fix_length_and_dec() { @@ -2575,7 +2874,7 @@ void Item_func_elt::fix_length_and_dec() set_if_bigger(decimals,args[i]->decimals); } fix_char_length(char_length); - set_persist_maybe_null(1); // NULL if wrong first arg + maybe_null=1; // NULL if wrong first arg } @@ -2770,7 +3069,7 @@ void Item_func_repeat::fix_length_and_dec() else { max_length= MAX_BLOB_WIDTH; - set_persist_maybe_null(1); + maybe_null= 1; } } @@ -2889,7 +3188,7 @@ void Item_func_rpad::fix_length_and_dec() else { max_length= MAX_BLOB_WIDTH; - set_persist_maybe_null(1); + maybe_null= 1; } } @@ -2995,7 +3294,7 @@ void Item_func_lpad::fix_length_and_dec() else { max_length= MAX_BLOB_WIDTH; - set_persist_maybe_null(1); + maybe_null= 1; } } @@ -3180,11 +3479,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 || @@ -3257,6 +3553,101 @@ 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; + 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(str))) + 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); + } + + if(tmp_length > current_thd->variables.max_allowed_packet) + { + push_warning_printf(current_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); + goto nl; + } + + if (tmp_value.alloc(tmp_length)) + goto nl; + + frm_length= cs->coll->strnxfrm(cs, + (uchar *) tmp_value.ptr(), tmp_length, + nweights ? nweights : tmp_length, + (const uchar *) res->ptr(), res->length(), + flags); + tmp_value.length(frm_length); + null_value= 0; + return &tmp_value; + +nl: + null_value= 1; + return 0; +} + + String *Item_func_hex::val_str_ascii(String *str) { String *res; @@ -3972,7 +4363,7 @@ bool Item_func_dyncol_create::fix_fields(THD *thd, Item **ref) void Item_func_dyncol_create::fix_length_and_dec() { - set_persist_maybe_null(1); + maybe_null= TRUE; collation.set(&my_charset_bin); decimals= 0; } @@ -4220,11 +4611,11 @@ String *Item_func_dyncol_create::val_str(String *str) if ((rc= ((names || force_names) ? mariadb_dyncol_create_many_named(&col, column_count, keys_str, vals, TRUE) : - mariadb_dyncol_create_many(&col, column_count, keys_num, - vals, TRUE)))) + mariadb_dyncol_create_many_num(&col, column_count, keys_num, + vals, TRUE)))) { dynamic_column_error_message(rc); - dynamic_column_column_free(&col); + mariadb_dyncol_free(&col); res= NULL; null_value= TRUE; } @@ -4363,11 +4754,11 @@ String *Item_func_dyncol_add::val_str(String *str) if ((rc= ((names || force_names) ? mariadb_dyncol_update_many_named(&col, column_count, keys_str, vals) : - mariadb_dyncol_update_many(&col, column_count, - keys_num, vals)))) + mariadb_dyncol_update_many_num(&col, column_count, + keys_num, vals)))) { dynamic_column_error_message(rc); - dynamic_column_column_free(&col); + mariadb_dyncol_free(&col); goto null; } @@ -4470,7 +4861,7 @@ 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= ((name == NULL) ? - mariadb_dyncol_get(&dyn_str, (uint) num, val) : + mariadb_dyncol_get_num(&dyn_str, (uint) num, val) : mariadb_dyncol_get_named(&dyn_str, name, val)))) { dynamic_column_error_message(rc); diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 86e1aa70a6b..6709b4b64c6 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -116,6 +116,27 @@ public: const char *func_name() const { return "sha2"; } }; +class Item_func_to_base64 :public Item_str_ascii_func +{ + String tmp_value; +public: + Item_func_to_base64(Item *a) :Item_str_ascii_func(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_func +{ + String tmp_value; +public: + Item_func_from_base64(Item *a) :Item_str_func(a) {} + String *val_str(String *); + void fix_length_and_dec(); + const char *func_name() const { return "from_base64"; } +}; + + class Item_func_aes_encrypt :public Item_str_func { public: @@ -157,7 +178,7 @@ public: { collation.set(system_charset_info); max_length= MAX_BLOB_WIDTH; - set_persist_maybe_null(1); + maybe_null= 1; } const char *func_name() const { return "decode_histogram"; } }; @@ -196,6 +217,49 @@ public: }; +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(Item *a, Item *b, Item *c) + :Item_str_func(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); + 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(Item *a, Item *b) + :Item_str_func(a, b) + {} + void cleanup() + { + DBUG_ENTER("Item_func_regex::cleanup"); + Item_str_func::cleanup(); + re.cleanup(); + DBUG_VOID_RETURN; + } + String *val_str(String *str); + void fix_length_and_dec(); + const char *func_name() const { return "regexp_substr"; } +}; + + class Item_func_insert :public Item_str_func { String tmp_value; @@ -379,7 +443,7 @@ public: String *val_str(String *); void fix_length_and_dec() { - set_persist_maybe_null(1); + maybe_null=1; /* 9 = MAX ((8- (arg_len % 8)) + 1) */ max_length = args[0]->max_length + 9; } @@ -395,7 +459,7 @@ public: String *val_str(String *); void fix_length_and_dec() { - set_persist_maybe_null(1); + maybe_null=1; /* 9 = MAX ((8- (arg_len % 8)) + 1) */ max_length= args[0]->max_length; if (max_length >= 9U) @@ -423,7 +487,7 @@ public: constructor_helper(); } String *val_str(String *); - void fix_length_and_dec() { set_persist_maybe_null(1); max_length = 13; } + void fix_length_and_dec() { maybe_null=1; max_length = 13; } const char *func_name() const { return "encrypt"; } bool check_vcol_func_processor(uchar *int_arg) { @@ -493,7 +557,7 @@ public: void fix_length_and_dec() { max_length= MAX_FIELD_NAME * system_charset_info->mbmaxlen; - set_persist_maybe_null(1); + maybe_null=1; } const char *func_name() const { return "database"; } const char *fully_qualified_func_name() const { return "database()"; } @@ -543,6 +607,28 @@ public: }; +class Item_func_current_role :public Item_func_sysconst +{ + Name_resolution_context *context; + +public: + Item_func_current_role(Name_resolution_context *context_arg) + : 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 ? 0 : &str_value); + } +}; + + class Item_func_soundex :public Item_str_func { String tmp_value; @@ -667,7 +753,7 @@ public: { collation.set(default_charset()); max_length=64; - set_persist_maybe_null(1); + maybe_null= 1; } }; @@ -694,7 +780,7 @@ public: Item_func_unhex(Item *a) :Item_str_func(a) { /* there can be bad hex strings */ - set_persist_maybe_null(1); + maybe_null= 1; } const char *func_name() const { return "unhex"; } String *val_str(String *); @@ -780,7 +866,7 @@ public: void fix_length_and_dec() { collation.set(&my_charset_bin, DERIVATION_COERCIBLE); - set_persist_maybe_null(1); + maybe_null=1; max_length=MAX_BLOB_WIDTH; } bool check_vcol_func_processor(uchar *int_arg) @@ -813,7 +899,7 @@ public: { decimals= 0; fix_length_and_charset(3 * 8 + 7, default_charset()); - set_persist_maybe_null(1); + maybe_null= 1; } }; @@ -940,7 +1026,7 @@ public: { collation.set(system_charset_info); max_length= 64 * collation.collation->mbmaxlen; // should be enough - set_persist_maybe_null(0); + maybe_null= 0; }; table_map not_null_tables() const { return 0; } }; @@ -955,11 +1041,31 @@ public: { collation.set(system_charset_info); max_length= 64 * collation.collation->mbmaxlen; // should be enough - set_persist_maybe_null(0); + maybe_null= 0; }; table_map not_null_tables() const { return 0; } }; +class Item_func_weight_string :public Item_str_func +{ + String tmp_value; + uint flags; + uint nweights; + uint result_length; +public: + Item_func_weight_string(Item *a, uint result_length_arg, + uint nweights_arg, uint flags_arg) + :Item_str_func(a) + { + 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(); +}; + class Item_func_crc32 :public Item_int_func { String value; @@ -1001,8 +1107,7 @@ class Item_func_uncompress: public Item_str_func String buffer; public: Item_func_uncompress(Item *a): Item_str_func(a){} - void fix_length_and_dec() - { set_persist_maybe_null(1); max_length= MAX_BLOB_WIDTH; } + 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 }; @@ -1067,7 +1172,7 @@ public: String *val_str(String *); void fix_length_and_dec() { - set_persist_maybe_null(1); + maybe_null= 1; collation.set(&my_charset_bin); decimals= 0; } @@ -1084,7 +1189,7 @@ public: :Item_str_func(str, num) {} void fix_length_and_dec() - { set_persist_maybe_null(1); max_length= MAX_BLOB_WIDTH; } + { maybe_null= 1;; max_length= MAX_BLOB_WIDTH; } /* Mark that collation can change between calls */ bool dynamic_result() { return 1; } @@ -1103,8 +1208,7 @@ class Item_func_dyncol_list: public Item_str_func { public: Item_func_dyncol_list(Item *str) :Item_str_func(str) {}; - void fix_length_and_dec() - { set_persist_maybe_null(1); max_length= MAX_BLOB_WIDTH; }; + 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 *); }; diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 87fa8147411..35e040cad3a 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1752,6 +1752,15 @@ Item_in_subselect::single_value_transformer(JOIN *join) */ where_item->walk(&Item::remove_dependence_processor, 0, (uchar *) select_lex->outer_select()); + /* + fix_field of substitution item will be done in time of + substituting. + Note that real_item() should be used instead of + original left expression because left_expr can be + runtime created Ref item which is deleted at the end + of the statement. Thus one of 'substitution' arguments + can be broken in case of PS. + */ substitution= func->create(left_expr, where_item); have_to_be_excluded= 1; if (thd->lex->describe) @@ -2268,11 +2277,11 @@ Item_in_subselect::create_row_in_to_exists_cond(JOIN * join, DBUG_RETURN(true); Item *item_eq= new Item_func_eq(new - Item_ref(&select_lex->context, - (*optimizer->get_cache())-> - addr(i), - (char *)"<no matter>", - (char *)in_left_expr_name), + 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, @@ -2991,7 +3000,7 @@ out: bool Item_in_subselect::select_in_like_transformer(JOIN *join) { - Query_arena *arena, backup; + Query_arena *arena= 0, backup; SELECT_LEX *current= thd->lex->current_select; const char *save_where= thd->where; bool trans_res= true; @@ -3013,9 +3022,6 @@ Item_in_subselect::select_in_like_transformer(JOIN *join) } } - if (changed) - DBUG_RETURN(false); - thd->where= "IN/ALL/ANY subquery"; /* @@ -3026,25 +3032,29 @@ Item_in_subselect::select_in_like_transformer(JOIN *join) note: we won't need Item_in_optimizer when handling degenerate cases like "... IN (SELECT 1)" */ + arena= thd->activate_stmt_arena_if_needed(&backup); if (!optimizer) { - arena= thd->activate_stmt_arena_if_needed(&backup); result= (!(optimizer= new Item_in_optimizer(left_expr, this))); - if (arena) - thd->restore_active_arena(arena, &backup); if (result) - goto err; + goto out; } thd->lex->current_select= current->return_after_parsing(); - result= (!left_expr->fixed && - left_expr->fix_fields(thd, optimizer->arguments())); + result= optimizer->fix_left(thd); /* fix_fields can change reference to left_expr, we need reassign it */ left_expr= optimizer->arguments()[0]; - thd->lex->current_select= current; + + if (changed) + { + trans_res= false; + goto out; + } + + if (result) - goto err; + goto out; /* Both transformers call fix_fields() only for Items created inside them, @@ -3053,7 +3063,6 @@ Item_in_subselect::select_in_like_transformer(JOIN *join) of Item, we have to call fix_fields() for it only with original arena to avoid memory leack) */ - arena= thd->activate_stmt_arena_if_needed(&backup); if (left_expr->cols() == 1) trans_res= single_value_transformer(join); else @@ -3068,9 +3077,9 @@ Item_in_subselect::select_in_like_transformer(JOIN *join) } trans_res= row_value_transformer(join); } +out: if (arena) thd->restore_active_arena(arena, &backup); -err: thd->where= save_where; DBUG_RETURN(trans_res); } @@ -3469,6 +3478,7 @@ int subselect_single_select_engine::prepare() select_lex->order_list.elements + select_lex->group_list.elements, select_lex->order_list.first, + false, select_lex->group_list.first, select_lex->having, NULL, select_lex, diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 483620bd2fa..a50443ad348 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -942,7 +942,7 @@ void Item_func_monthname::fix_length_and_dec() collation.set(cs, DERIVATION_COERCIBLE, repertoire); decimals=0; max_length= locale->max_month_name_length * collation.collation->mbmaxlen; - set_persist_maybe_null(1); + maybe_null=1; } @@ -1089,7 +1089,7 @@ void Item_func_dayname::fix_length_and_dec() collation.set(cs, DERIVATION_COERCIBLE, repertoire); decimals=0; max_length= locale->max_day_name_length * collation.collation->mbmaxlen; - set_persist_maybe_null(1); + maybe_null=1; } @@ -1290,7 +1290,19 @@ bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval) String str_value(buf, sizeof(buf), &my_charset_bin); bzero((char*) interval,sizeof(*interval)); - if ((int) int_type <= INTERVAL_MICROSECOND) + if (int_type == INTERVAL_SECOND && args->decimals) + { + my_decimal decimal_value, *val; + ulonglong second; + ulong second_part; + if (!(val= args->val_decimal(&decimal_value))) + return true; + interval->neg= my_decimal2seconds(val, &second, &second_part); + interval->second= second; + interval->second_part= second_part; + return false; + } + else if ((int) int_type <= INTERVAL_MICROSECOND) { value= args->val_int(); if (args->null_value) @@ -1435,12 +1447,12 @@ bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval) void Item_temporal_func::fix_length_and_dec() { - static const uint max_time_type_width[5]= - { MAX_DATETIME_WIDTH, MAX_DATETIME_WIDTH, MAX_DATE_WIDTH, - MAX_DATETIME_WIDTH, MIN_TIME_WIDTH }; - uint char_length= max_time_type_width[mysql_type_to_time_type(field_type())+2]; - - set_persist_maybe_null(1); + uint char_length= mysql_temporal_int_part_length(field_type()); + /* + We set maybe_null to 1 as default as any bad argument with date or + time can get us to return NULL. + */ + maybe_null= 1; if (decimals) { if (decimals == NOT_FIXED_DEC) @@ -1529,7 +1541,7 @@ void Item_func_curdate::fix_length_and_dec() ltime.hour= ltime.minute= ltime.second= 0; ltime.time_type= MYSQL_TIMESTAMP_DATE; Item_datefunc::fix_length_and_dec(); - set_persist_maybe_null(0); + maybe_null= false; } /** @@ -1768,7 +1780,7 @@ void Item_func_date_format::fix_length_and_dec() collation.collation->mbmaxlen; set_if_smaller(max_length,MAX_BLOB_WIDTH); } - set_persist_maybe_null(1); // If wrong date + maybe_null=1; // If wrong date } @@ -1949,7 +1961,7 @@ bool Item_func_from_unixtime::get_date(MYSQL_TIME *ltime, void Item_func_convert_tz::fix_length_and_dec() { - decimals= args[0]->decimals; + decimals= args[0]->temporal_precision(MYSQL_TYPE_DATETIME); Item_temporal_func::fix_length_and_dec(); } @@ -2020,28 +2032,40 @@ void Item_date_add_interval::fix_length_and_dec() */ cached_field_type= MYSQL_TYPE_STRING; arg0_field_type= args[0]->field_type(); + uint interval_dec= 0; + if (int_type == INTERVAL_MICROSECOND || + (int_type >= INTERVAL_DAY_MICROSECOND && + int_type <= INTERVAL_SECOND_MICROSECOND)) + interval_dec= TIME_SECOND_PART_DIGITS; + else if (int_type == INTERVAL_SECOND && args[1]->decimals > 0) + 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= 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) { if (int_type <= INTERVAL_DAY || int_type == INTERVAL_YEAR_MONTH) cached_field_type= arg0_field_type; else + { + decimals= interval_dec; cached_field_type= MYSQL_TYPE_DATETIME; + } } else if (arg0_field_type == MYSQL_TYPE_TIME) { + 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; } - if (int_type == INTERVAL_MICROSECOND || int_type >= INTERVAL_DAY_MICROSECOND) - decimals= 6; else - decimals= args[0]->decimals; - + decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec); Item_temporal_func::fix_length_and_dec(); } @@ -2052,7 +2076,9 @@ bool Item_date_add_interval::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { INTERVAL interval; - if (args[0]->get_date(ltime, 0) || + if (args[0]->get_date(ltime, + cached_field_type == MYSQL_TYPE_TIME ? + TIME_TIME_ONLY : 0) || get_interval_value(args[1], int_type, &interval)) return (null_value=1); @@ -2117,7 +2143,7 @@ void Item_extract::print(String *str, enum_query_type query_type) void Item_extract::fix_length_and_dec() { - set_persist_maybe_null(1); // If wrong date + 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; @@ -2443,7 +2469,9 @@ bool Item_time_typecast::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) if (ltime->time_type != MYSQL_TIMESTAMP_TIME) ltime->year= ltime->month= ltime->day= 0; ltime->time_type= MYSQL_TIMESTAMP_TIME; - return 0; + return (fuzzy_date & TIME_TIME_ONLY) ? 0 : + (null_value= check_date_with_warn(ltime, fuzzy_date, + MYSQL_TIMESTAMP_ERROR)); } @@ -2551,10 +2579,19 @@ void Item_func_add_time::fix_length_and_dec() arg0_field_type= args[0]->field_type(); if (arg0_field_type == MYSQL_TYPE_DATE || arg0_field_type == MYSQL_TYPE_DATETIME || - arg0_field_type == MYSQL_TYPE_TIMESTAMP) + arg0_field_type == MYSQL_TYPE_TIMESTAMP || + is_date) + { cached_field_type= MYSQL_TYPE_DATETIME; + 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= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME), + args[1]->temporal_precision(MYSQL_TYPE_TIME)); + } Item_temporal_func::fix_length_and_dec(); } @@ -2735,13 +2772,14 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) { DBUG_ASSERT(fixed == 1); bool overflow= 0; - longlong hour= args[0]->val_int(); longlong minute= args[1]->val_int(); - longlong second= args[2]->val_int(); + ulonglong second; + ulong microsecond; + bool neg= args[2]->get_seconds(&second, µsecond); if (args[0]->null_value || args[1]->null_value || args[2]->null_value || - minute < 0 || minute > 59 || second < 0 || second > 59) + minute < 0 || minute > 59 || neg || second > 59) return (null_value= 1); bzero(ltime, sizeof(*ltime)); @@ -2763,6 +2801,7 @@ bool Item_func_maketime::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) ltime->hour= (uint) ((hour < 0 ? -hour : hour)); ltime->minute= (uint) minute; ltime->second= (uint) second; + ltime->second_part= microsecond; } else { @@ -3078,13 +3117,25 @@ get_date_time_result_type(const char *format, uint length) void Item_func_str_to_date::fix_length_and_dec() { + if (agg_arg_charsets(collation, args, 2, MY_COLL_ALLOW_CONV, 1)) + return; + if (collation.collation->mbminlen > 1) + { +#if MYSQL_VERSION_ID > 50500 + internal_charset= &my_charset_utf8mb4_general_ci; +#else + internal_charset= &my_charset_utf8_general_ci; +#endif + } + cached_field_type= MYSQL_TYPE_DATETIME; decimals= NOT_FIXED_DEC; if ((const_item= args[1]->const_item())) { char format_buff[64]; String format_str(format_buff, sizeof(format_buff), &my_charset_bin); - String *format= args[1]->val_str(&format_str); + String *format= args[1]->val_str(&format_str, &format_converter, + internal_charset); decimals= 0; if (!args[1]->null_value) { @@ -3122,8 +3173,8 @@ bool Item_func_str_to_date::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date) String format_str(format_buff, sizeof(format_buff), &my_charset_bin), *format; - val= args[0]->val_str(&val_string); - format= args[1]->val_str(&format_str); + val= args[0]->val_str(&val_string, &subject_converter, internal_charset); + format= args[1]->val_str(&format_str, &format_converter, internal_charset); if (args[0]->null_value || args[1]->null_value) return (null_value=1); diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index 029e3b17cf1..99c41961aba 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -30,19 +30,17 @@ enum date_time_format_types TIME_ONLY= 0, TIME_MICROSECOND, DATE_ONLY, DATE_TIME, DATE_TIME_MICROSECOND }; -static inline enum enum_mysql_timestamp_type -mysql_type_to_time_type(enum enum_field_types mysql_type) + +static inline uint +mysql_temporal_int_part_length(enum enum_field_types mysql_type) { - switch(mysql_type) { - case MYSQL_TYPE_TIME: return MYSQL_TIMESTAMP_TIME; - case MYSQL_TYPE_TIMESTAMP: - case MYSQL_TYPE_DATETIME: return MYSQL_TIMESTAMP_DATETIME; - case MYSQL_TYPE_NEWDATE: - case MYSQL_TYPE_DATE: return MYSQL_TIMESTAMP_DATE; - default: return MYSQL_TIMESTAMP_ERROR; - } + static uint max_time_type_width[5]= + { MAX_DATETIME_WIDTH, MAX_DATETIME_WIDTH, MAX_DATE_WIDTH, + MAX_DATETIME_WIDTH, MIN_TIME_WIDTH }; + return max_time_type_width[mysql_type_to_time_type(mysql_type)+2]; } + bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval); class Item_func_period_add :public Item_int_func @@ -82,7 +80,7 @@ public: { decimals=0; max_length=6*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } enum_monotonicity_info get_monotonicity_info() const; longlong val_int_endpoint(bool left_endp, bool *incl_endp); @@ -105,7 +103,7 @@ public: { decimals=0; max_length=6*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null= 1; } enum_monotonicity_info get_monotonicity_info() const; longlong val_int_endpoint(bool left_endp, bool *incl_endp); @@ -138,7 +136,7 @@ public: { decimals=0; max_length=2*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -170,7 +168,7 @@ public: { decimals= 0; fix_char_length(2); - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -208,7 +206,7 @@ public: { decimals= 0; fix_char_length(3); - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -229,7 +227,7 @@ public: { decimals=0; max_length=2*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -250,7 +248,7 @@ public: { decimals=0; max_length=2*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -271,7 +269,7 @@ public: { decimals=0; max_length=1*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -292,7 +290,7 @@ public: { decimals=0; max_length=2*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -313,7 +311,7 @@ public: { decimals=0; max_length=2*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } }; @@ -327,7 +325,7 @@ public: { decimals=0; max_length=6*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -350,7 +348,7 @@ public: { decimals=0; max_length=4*MY_CHARSET_BIN_MB_MAXLEN; - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -384,7 +382,7 @@ public: { decimals= 0; fix_char_length(1); - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -410,26 +408,32 @@ class Item_func_dayname :public Item_func_weekday 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) {} void fix_num_length_and_dec() { if (arg_count) - decimals= args[0]->decimals; + decimals= args[0]->temporal_precision(arg0_expected_type()); set_if_smaller(decimals, TIME_SECOND_PART_DIGITS); max_length=17 + (decimals ? decimals + 1 : 0); - set_persist_maybe_null(1); + maybe_null= true; } - void find_num_type() { hybrid_type= decimals ? DECIMAL_RESULT : INT_RESULT; } + void find_num_type() + { cached_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; } + bool date_op(MYSQL_TIME *ltime, uint fuzzydate) { DBUG_ASSERT(0); return true; } }; class Item_func_unix_timestamp :public Item_func_seconds_hybrid { bool get_timestamp_value(my_time_t *seconds, ulong *second_part); +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) {} @@ -461,12 +465,14 @@ public: 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) {} const char *func_name() const { return "time_to_sec"; } void fix_num_length_and_dec() { - set_persist_maybe_null(1); + maybe_null= true; Item_func_seconds_hybrid::fix_num_length_and_dec(); } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} @@ -576,7 +582,7 @@ public: { store_now_in_TIME(<ime); Item_timefunc::fix_length_and_dec(); - set_persist_maybe_null(0); + maybe_null= false; } bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); /* @@ -658,7 +664,7 @@ public: { store_now_in_TIME(<ime); Item_temporal_func::fix_length_and_dec(); - set_persist_maybe_null(0); + maybe_null= false; } bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date); virtual void store_now_in_TIME(MYSQL_TIME *now_time)=0; @@ -703,7 +709,7 @@ public: void update_used_tables() { Item_func_now::update_used_tables(); - set_persist_maybe_null(0); + maybe_null= 0; used_tables_cache|= RAND_TABLE_BIT; } }; @@ -896,7 +902,7 @@ public: void fix_length_and_dec() { if (decimals == NOT_FIXED_DEC) - decimals= args[0]->decimals; + decimals= args[0]->temporal_precision(field_type()); Item_temporal_func::fix_length_and_dec(); } }; @@ -968,7 +974,8 @@ public: const char *func_name() const { return "timediff"; } void fix_length_and_dec() { - decimals= MY_MAX(args[0]->decimals, args[1]->decimals); + 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); @@ -980,6 +987,11 @@ public: Item_func_maketime(Item *a, Item *b, Item *c) :Item_timefunc(a, b, c) {} + void fix_length_and_dec() + { + decimals= MY_MIN(args[2]->decimals, TIME_SECOND_PART_DIGITS); + Item_timefunc::fix_length_and_dec(); + } const char *func_name() const { return "maketime"; } bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date); }; @@ -994,7 +1006,7 @@ public: void fix_length_and_dec() { decimals=0; - set_persist_maybe_null(1); + maybe_null=1; } bool check_partition_func_processor(uchar *int_arg) {return FALSE;} bool check_vcol_func_processor(uchar *int_arg) { return FALSE;} @@ -1016,7 +1028,7 @@ public: void fix_length_and_dec() { decimals=0; - set_persist_maybe_null(1); + maybe_null=1; } virtual void print(String *str, enum_query_type query_type); }; @@ -1038,7 +1050,7 @@ public: const char *func_name() const { return "get_format"; } void fix_length_and_dec() { - set_persist_maybe_null(1); + maybe_null= 1; decimals=0; fix_length_and_charset(17, default_charset()); } @@ -1050,9 +1062,13 @@ class Item_func_str_to_date :public Item_temporal_hybrid_func { timestamp_type cached_timestamp_type; bool const_item; + String subject_converter; + 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_temporal_hybrid_func(a, b), const_item(false), + internal_charset(NULL) {} bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date); const char *func_name() const { return "str_to_date"; } diff --git a/sql/item_xmlfunc.cc b/sql/item_xmlfunc.cc index 1aab6b45c74..456779beec1 100644 --- a/sql/item_xmlfunc.cc +++ b/sql/item_xmlfunc.cc @@ -2604,7 +2604,6 @@ void Item_xml_str_func::fix_length_and_dec() status_var_increment(current_thd->status_var.feature_xml); nodeset_func= 0; - set_persist_maybe_null(1); if (agg_arg_charsets_for_comparison(collation, args, arg_count)) return; diff --git a/sql/item_xmlfunc.h b/sql/item_xmlfunc.h index 3356b4ac902..800cf6ed760 100644 --- a/sql/item_xmlfunc.h +++ b/sql/item_xmlfunc.h @@ -34,10 +34,14 @@ protected: public: Item_xml_str_func(Item *a, Item *b): Item_str_func(a,b) - {} + { + maybe_null= TRUE; + } Item_xml_str_func(Item *a, Item *b, Item *c): Item_str_func(a,b,c) - {} + { + maybe_null= TRUE; + } void fix_length_and_dec(); String *parse_xml(String *raw_xml, String *parsed_xml_buf); bool check_vcol_func_processor(uchar *int_arg) diff --git a/sql/lex.h b/sql/lex.h index c5229beb653..88d73b9b169 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -59,6 +59,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)}, @@ -154,6 +155,7 @@ static SYMBOL symbols[] = { { "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)}, @@ -266,6 +268,7 @@ 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)}, { "IGNORE", SYM(IGNORE_SYM)}, @@ -491,10 +494,13 @@ static SYMBOL symbols[] = { { "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)}, @@ -645,6 +651,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)}, diff --git a/sql/log.cc b/sql/log.cc index 9462c28301d..c09e2b416c8 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -91,6 +91,7 @@ ulong opt_binlog_dbug_fsync_sleep= 0; #endif mysql_mutex_t LOCK_prepare_ordered; +mysql_cond_t COND_prepare_ordered; mysql_mutex_t LOCK_commit_ordered; static ulonglong binlog_status_var_num_commits; @@ -578,6 +579,8 @@ void thd_binlog_rollback_stmt(THD * thd) (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); if (cache_mngr) cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF); } + +#ifdef REMOVED /* Write the contents of a cache to memory buffer. @@ -615,7 +618,8 @@ int wsrep_write_cache(IO_CACHE *cache, uchar **buf, int *buf_len) if (total_length > 0) { *buf_len += length; - *buf = (uchar *)my_realloc(*buf, total_length+length, MYF(0)); + *buf = (uchar *)my_realloc(*buf, total_length+length, + MYF(MY_ALLOW_ZERO_PTR)); if (!*buf) { WSREP_ERROR("io cache write problem: %d %d", *buf_len, length); @@ -645,8 +649,8 @@ int wsrep_write_cache(IO_CACHE *cache, uchar **buf, int *buf_len) return 0; } +#endif /* REMOVED */ #endif - /* 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) @@ -3005,6 +3009,15 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, "Yes" : "No"), thd->query_plan_fsort_passes) == (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_query(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) @@ -3883,7 +3896,8 @@ err: 1 error */ -bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log) +bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log, + rpl_gtid *init_state, uint32 init_state_len) { LOG_INFO linfo; bool error=0; @@ -3902,6 +3916,14 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log) if (!is_relay_log) { + if (init_state && !is_empty_state()) + { + my_error(ER_BINLOG_MUST_BE_EMPTY, MYF(0)); + mysql_mutex_unlock(&LOCK_index); + mysql_mutex_unlock(&LOCK_log); + DBUG_RETURN(1); + } + /* Mark that a RESET MASTER is in progress. This ensures that a binlog checkpoint will not try to write binlog @@ -4019,7 +4041,10 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log) if (!is_relay_log) { - rpl_global_gtid_binlog_state.reset(); + 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 */ @@ -4974,12 +4999,23 @@ end: } -bool MYSQL_BIN_LOG::append(Log_event* 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_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 @@ -4997,7 +5033,6 @@ bool MYSQL_BIN_LOG::append(Log_event* ev) 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); } @@ -5545,7 +5580,7 @@ MYSQL_BIN_LOG::flush_and_set_pending_rows_event(THD *thd, /* 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) + bool is_transactional, uint64 commit_id) { rpl_gtid gtid; uint32 domain_id= thd->variables.gtid_domain_id; @@ -5583,7 +5618,8 @@ MYSQL_BIN_LOG::write_gtid_event(THD *thd, bool standalone, return true; Gtid_log_event gtid_event(thd, seq_no, domain_id, standalone, - LOG_EVENT_SUPPRESS_USE_F, is_transactional); + LOG_EVENT_SUPPRESS_USE_F, is_transactional, + commit_id); /* Write the event to the binary log. */ if (gtid_event.write(&mysql_bin_log.log_file)) @@ -5715,6 +5751,30 @@ MYSQL_BIN_LOG::append_state_pos(String *str) bool +MYSQL_BIN_LOG::append_state(String *str) +{ + bool err; + + mysql_mutex_lock(&rpl_global_gtid_binlog_state.LOCK_binlog_state); + err= rpl_global_gtid_binlog_state.append_state(str); + mysql_mutex_unlock(&rpl_global_gtid_binlog_state.LOCK_binlog_state); + return err; +} + + +bool +MYSQL_BIN_LOG::is_empty_state() +{ + bool res; + + mysql_mutex_lock(&rpl_global_gtid_binlog_state.LOCK_binlog_state); + res= (rpl_global_gtid_binlog_state.count() == 0); + mysql_mutex_unlock(&rpl_global_gtid_binlog_state.LOCK_binlog_state); + return res; +} + + +bool MYSQL_BIN_LOG::find_in_binlog_state(uint32 domain_id, uint32 server_id, rpl_gtid *out_gtid) { @@ -5817,7 +5877,9 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) could have changed since. */ #ifdef WITH_WSREP - if ((WSREP(thd) && wsrep_emulate_bin_log) || is_open()) + /* applier and replayer can skip writing binlog events */ + if ((WSREP_EMULATE_BINLOG(thd) && (thd->wsrep_exec_mode != REPL_RECV)) || + is_open()) #else if (likely(is_open())) #endif @@ -5845,7 +5907,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) my_org_b_tell= my_b_tell(file); mysql_mutex_lock(&LOCK_log); prev_binlog_id= current_binlog_id; - if (write_gtid_event(thd, true, using_trans)) + if (write_gtid_event(thd, true, using_trans, 0)) goto err; } else @@ -6002,41 +6064,6 @@ err: } } -#ifdef WITH_WSREP - if (WSREP(thd) && wsrep_incremental_data_collection && - (wsrep_emulate_bin_log || mysql_bin_log.is_open())) - { - DBUG_ASSERT(thd->wsrep_ws_handle.trx_id != (unsigned long)-1); - if (!error) - { - IO_CACHE* cache= get_trans_log(thd); - uchar* buf= NULL; - int buf_len= 0; - - if (wsrep_emulate_bin_log) - thd->binlog_flush_pending_rows_event(false); - error= wsrep_write_cache(cache, &buf, &buf_len); - if (!error && buf_len > 0) - { - const struct wsrep_buf buff = { buf, buf_len }; - - const bool nocopy(false); - const bool unordered(false); - - wsrep_status_t rc= wsrep->append_data(wsrep, - &thd->wsrep_ws_handle, - &buff, 1, WSREP_DATA_ORDERED, - true); - if (rc != WSREP_OK) - { - sql_print_warning("WSREP: append_data() returned %d", rc); - error= 1; - } - } - if (buf_len) my_free(buf); - } - } -#endif /* WITH_WSREP */ DBUG_RETURN(error); } @@ -6782,45 +6809,284 @@ MYSQL_BIN_LOG::write_transaction_to_binlog(THD *thd, } } + +/* + 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. + + @retval TRUE If queued as the first entry in the queue (meaning this + is the leader) + @retval FALSE Otherwise +*/ + bool -MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) +MYSQL_BIN_LOG::queue_for_group_commit(group_commit_entry *orig_entry) { + group_commit_entry *entry, *orig_queue; + wait_for_commit *list, *cur, *last; + wait_for_commit *wfc; + DBUG_ENTER("MYSQL_BIN_LOG::queue_for_group_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. + 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->waiting_for_commit) + { + mysql_mutex_lock(&wfc->LOCK_wait_commit); + /* Do an extra check here, this time safely under lock. */ + if (wfc->waiting_for_commit) + { + /* + 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. - entry->thd->clear_wakeup_ready(); + 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. + */ + wfc->opaque_pointer= orig_entry; + DEBUG_SYNC(orig_entry->thd, "group_commit_waiting_for_prior"); + do + { + mysql_cond_wait(&wfc->COND_wait_commit, &wfc->LOCK_wait_commit); + } while (wfc->waiting_for_commit); + wfc->opaque_pointer= NULL; + DBUG_PRINT("info", ("After waiting for prior commit, queued_by_other=%d", + orig_entry->queued_by_other)); + } + mysql_mutex_unlock(&wfc->LOCK_wait_commit); + } + + /* + 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(false); + + /* 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; if not we just wake it up. + + 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 all the waiters that need to be processed in `list', + linked through the next_subsequent_commit pointer. Initially this list + contains only the entry passed into this function. - if (entry->cache_mngr->using_xa) + We process entries in the list one by one. The element currently being + processed is pointed to by `cur`, 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 element, it is first added to the group_commit_queue. + Then any waiters for that element are added at the end of the list, to + be processed in subsequent iterations. This continues until the list + is exhausted, with all elements ever added eventually processed. + + The end result is a breath-first traversal of the tree of waiters, + re-using the next_subsequent_commit pointers in place of extra stack + space in a recursive traversal. + + The temporary list created in next_subsequent_commit is not + used by the caller or any other function. + */ + + list= wfc; + cur= list; + last= list; + 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"); + /* Add the entry to the group commit queue. */ + entry->next= group_commit_queue; + group_commit_queue= 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) + break; // Can happen if initial entry has no wait_for_commit + + /* + 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) + { + bool have_lock; + wait_for_commit *waiter; + + mysql_mutex_lock(&cur->LOCK_wait_commit); + have_lock= true; + /* + Grab the list, now safely under lock, and process it if still + non-empty. + */ + waiter= cur->subsequent_commits_list; + cur->subsequent_commits_list= NULL; + while (waiter) + { + wait_for_commit *next= 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 put it at the end of the list to be processed in a subsequent + iteration of the outer loop. + */ + entry2->queued_by_other= true; + last->next_subsequent_commit= waiter; + last= waiter; + /* + As a small optimisation, we do not actually need to set + waiter->next_subsequent_commit to NULL, as we can use the + pointer `last' to check for end-of-list. + */ + } + else + { + /* + Wake up the waiting transaction. + + For this, we need to set the "wakeup running" flag and release + the waitee lock to avoid a deadlock, see comments on + THD::wakeup_subsequent_commits2() for details. + */ + if (have_lock) + { + have_lock= false; + cur->wakeup_subsequent_commits_running= true; + mysql_mutex_unlock(&cur->LOCK_wait_commit); + } + waiter->wakeup(0); + } + waiter= next; + } + if (have_lock) + mysql_mutex_unlock(&cur->LOCK_wait_commit); + } + if (cur == last) + break; + /* + Move to the next entry in the flattened list of waiting transactions + that still need to be processed transitively. + */ + cur= cur->next_subsequent_commit; + entry= (group_commit_entry *)cur->opaque_pointer; + DBUG_ASSERT(entry != NULL); } + + /* + Now we need to clear the wakeup_subsequent_commits_running flags. + + 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(), which was called above for any other thread + that might modify the list in parallel, does a full memory barrier already + (it locks a mutex). + */ + if (list) + { + for (;;) + { + list->wakeup_subsequent_commits_running= false; + if (list == last) + break; + list= list->next_subsequent_commit; + } + } + + if (opt_binlog_commit_wait_count > 0) + 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) +{ + bool 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) + 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"); @@ -6839,7 +7105,20 @@ MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) 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 { @@ -6909,6 +7188,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) group_commit_entry *queue= NULL; bool check_purge= false; ulong binlog_id; + uint64 commit_id; DBUG_ENTER("MYSQL_BIN_LOG::trx_group_commit_leader"); LINT_INIT(binlog_id); @@ -6919,12 +7199,18 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) */ mysql_mutex_lock(&LOCK_log); DEBUG_SYNC(leader->thd, "commit_after_get_LOCK_log"); - binlog_id= current_binlog_id; 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; @@ -6943,6 +7229,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) DBUG_ASSERT(is_open()); if (likely(is_open())) // Should always be true { + commit_id= (last_in_queue == leader ? 0 : (uint64)leader->thd->query_id); /* Commit every transaction in the queue. @@ -6963,7 +7250,7 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) */ DBUG_ASSERT(!cache_mngr->stmt_cache.empty() || !cache_mngr->trx_cache.empty()); - if ((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); @@ -7123,7 +7410,12 @@ 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"); @@ -7138,11 +7430,12 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) 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; - if (write_gtid_event(entry->thd, false, entry->using_trx_cache)) + if (write_gtid_event(entry->thd, false, entry->using_trx_cache, commit_id)) return ER_ERROR_ON_WRITE; if (entry->using_stmt_cache && !mngr->stmt_cache.empty() && @@ -7210,6 +7503,72 @@ MYSQL_BIN_LOG::write_transaction_or_stmt(group_commit_entry *entry) 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) + 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) + break; + head= group_commit_queue; + for (e= head; e && e != last_head; e= e->next) + ++count; + if (count >= opt_binlog_commit_wait_count) + break; + last_head= head; + } + + /* + 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); + } +} + + /** Wait until we get a signal that the relay log has been updated. @@ -7751,6 +8110,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); diff --git a/sql/log.h b/sql/log.h index 487005913c9..feb79a796f1 100644 --- a/sql/log.h +++ b/sql/log.h @@ -45,6 +45,15 @@ 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; @@ -76,9 +85,11 @@ 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_commit_ordered; #ifdef HAVE_PSI_INTERFACE extern PSI_mutex_key key_LOCK_prepare_ordered, key_LOCK_commit_ordered; +extern PSI_cond_key key_COND_prepare_ordered; #endif class TC_LOG_DUMMY: public TC_LOG // use it to disable the logging @@ -397,6 +408,7 @@ private: class binlog_cache_mngr; struct rpl_gtid; +struct wait_for_commit; class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG { private: @@ -445,6 +457,8 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG 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; }; @@ -525,7 +539,8 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG int new_file_impl(bool need_lock); void do_checkpoint_request(ulong binlog_id); void purge(); - int write_transaction_or_stmt(group_commit_entry *entry); + int write_transaction_or_stmt(group_commit_entry *entry, uint64 commit_id); + bool 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); bool is_xidlist_idle_nolock(); @@ -672,6 +687,7 @@ public: } void set_max_size(ulong max_size_arg); void signal_update(); + void wait_for_sufficient_commits(); void wait_for_update_relay_log(THD* thd); int wait_for_update_bin_log(THD* thd, const struct timespec * timeout); void init(ulong max_size); @@ -712,6 +728,7 @@ public: */ bool appendv(const char* 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); @@ -751,7 +768,8 @@ 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 create_new_log); + bool reset_logs(THD* thd, bool create_new_log, + rpl_gtid *init_state, uint32 init_state_len); void close(uint exiting); void clear_inuse_flag_when_closing(File file); @@ -775,11 +793,14 @@ public: 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); + 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); diff --git a/sql/log_event.cc b/sql/log_event.cc index 450a80bc9b2..272f0da6735 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -128,7 +128,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) { @@ -361,7 +361,6 @@ 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) @@ -935,8 +934,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 @@ -961,22 +963,29 @@ int Log_event::do_update_pos(Relay_log_info *rli) 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), - thd); + /* + 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. + */ + if (!rgi->is_parallel_exec || is_group_event(get_type_code())) + rli->stmt_done(log_pos, + (is_artificial_event() && + IF_DBUG(debug_not_change_ts_if_art_event > 0, 1) ? + 0 : when), + thd, rgi); 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; ); } - 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) +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: %lu", @@ -1753,6 +1762,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, "#\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, "#\n", 2); +} + /* Log_event::print_header() */ @@ -1787,86 +1955,27 @@ void Log_event::print_header(IO_CACHE* file, { 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, "# Event: ", 9); } DBUG_VOID_RETURN; } @@ -2572,11 +2681,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 @@ -2801,17 +2910,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) @@ -3805,9 +3919,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); } /** @@ -3856,15 +3970,20 @@ 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) { LEX_STRING new_db; int expected_error,actual_error= 0; HA_CREATE_INFO db_options; uint64 sub_id= 0; rpl_gtid gtid; + Relay_log_info const *rli= rgi->rli; +#ifdef WITH_WSREP + Rpl_filter *rpl_filter= (rli->mi) ? rli->mi->rpl_filter: NULL; +#else Rpl_filter *rpl_filter= rli->mi->rpl_filter; +#endif /* WITH_WSREP */ DBUG_ENTER("Query_log_event::do_apply_event"); /* @@ -3888,21 +4007,10 @@ 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) + if (strcmp("COMMIT", query) == 0 && rgi->tables_to_lock) { /* Cleaning-up the last statement context: @@ -3911,7 +4019,7 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, */ int error; char llbuff[22]; - if ((error= rows_event_stmt_cleanup(const_cast<Relay_log_info*>(rli), thd))) + if ((error= rows_event_stmt_cleanup(rgi, thd))) { const_cast<Relay_log_info*>(rli)->report(ERROR_LEVEL, error, "Error in cleaning up after an event preceeding the commit; " @@ -3926,12 +4034,11 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, 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); + rgi->inc_event_relay_log_pos(); } else { - const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); + rgi->slave_close_thread_tables(thd); } /* @@ -4056,12 +4163,12 @@ 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 (strcmp("COMMIT", query) == 0 && (sub_id= rli->gtid_sub_id)) + if (strcmp("COMMIT", query) == 0 && (sub_id= rgi->gtid_sub_id)) { /* Clear the GTID from the RLI so we don't accidentally reuse it. */ - const_cast<Relay_log_info*>(rli)->gtid_sub_id= 0; + rgi->gtid_sub_id= 0; - gtid= rli->current_gtid; + gtid= rgi->current_gtid; if (rpl_global_gtid_slave_state.record_gtid(thd, >id, sub_id, true, false)) { rli->report(ERROR_LEVEL, ER_CANNOT_UPDATE_GTID_STATE, @@ -4294,7 +4401,7 @@ end: DBUG_RETURN(thd->is_slave_error); } -int Query_log_event::do_update_pos(Relay_log_info *rli) +int Query_log_event::do_update_pos(rpl_group_info *rgi) { /* Note that we will not increment group* positions if we are just @@ -4303,20 +4410,22 @@ int Query_log_event::do_update_pos(Relay_log_info *rli) */ if (thd->one_shot_set) { - 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 -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_ASSERT(query && q_len > 0); + DBUG_ASSERT(thd == rgi->thd); /* An event skipped due to @@skip_replication must not be counted towards the @@ -4328,13 +4437,13 @@ 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)); + 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; DBUG_RETURN(Log_event::EVENT_SKIP_COUNT); @@ -4355,7 +4464,7 @@ Query_log_event::do_shall_skip(Relay_log_info *rli) } } #endif - DBUG_RETURN(Log_event::do_shall_skip(rli)); + DBUG_RETURN(Log_event::do_shall_skip(rgi)); } @@ -4474,7 +4583,7 @@ Start_log_event_v3::Start_log_event_v3(const char* buf, *description_event) :Log_event(buf, description_event) { - 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); @@ -4525,10 +4634,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: @@ -4541,24 +4652,14 @@ int Start_log_event_v3::do_apply_event(Relay_log_info const *rli) */ if (created) { - error= close_temporary_tables(thd); + rli->close_temporary_tables(); + /* The following is only false if we get here with a BINLOG statement */ if (rli->mi) cleanup_load_tmpdir(&rli->mi->cmp_connection_name); } - else - { - /* - 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. - */ - TABLE *table; - for (table= thd->temporary_tables; table; table= table->next) - table->in_use= thd; - } break; /* @@ -4573,7 +4674,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 @@ -4821,16 +4922,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 @@ -4843,7 +4943,7 @@ bool Format_description_log_event::write(IO_CACHE* file) #ifndef DBUG_OFF data_written= 0; // to prepare for need_checksum assert #endif - buff[FORMAT_DESCRIPTION_HEADER_LEN]= need_checksum() ? + uchar checksum_byte= need_checksum() ? checksum_alg : (uint8) BINLOG_CHECKSUM_ALG_OFF; /* FD of checksum-aware server is always checksum-equipped, (V) is in, @@ -4863,7 +4963,10 @@ 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) || + wrapper_my_b_safe_write(file, buff, sizeof(buff)) || + wrapper_my_b_safe_write(file, (uchar*)post_header_len, + number_of_event_types) || + wrapper_my_b_safe_write(file, &checksum_byte, sizeof(checksum_byte)) || write_footer(file)); if (no_checksum) checksum_alg= BINLOG_CHECKSUM_ALG_OFF; @@ -4872,9 +4975,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 const *rli= rgi->rli; DBUG_ENTER("Format_description_log_event::do_apply_event"); /* @@ -4896,7 +5000,7 @@ int Format_description_log_event::do_apply_event(Relay_log_info const *rli) "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); } /* @@ -4915,7 +5019,7 @@ 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) @@ -4928,7 +5032,7 @@ int Format_description_log_event::do_apply_event(Relay_log_info const *rli) 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) global_system_variables.server_id) { @@ -4945,17 +5049,17 @@ 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; } @@ -5054,9 +5158,9 @@ uint8 get_checksum_alg(const char* buf, ulong len) 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); @@ -5576,10 +5680,11 @@ 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) { LEX_STRING new_db; + Relay_log_info const *rli= rgi->rli; Rpl_filter *rpl_filter= rli->mi->rpl_filter; DBUG_ENTER("Load_log_event::do_apply_event"); @@ -5592,25 +5697,15 @@ int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); /* 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, 0); + mysql_reset_thd_for_next_command(thd); - 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 @@ -5844,7 +5939,7 @@ Error '%s' running LOAD DATA INFILE on table '%s'. Default database: '%s'", 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 @@ -5929,16 +6024,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)); @@ -5979,8 +6072,9 @@ bool Rotate_log_event::write(IO_CACHE* file) @retval 0 ok */ -int Rotate_log_event::do_update_pos(Relay_log_info *rli) +int Rotate_log_event::do_update_pos(rpl_group_info *rgi) { + Relay_log_info *rli= rgi->rli; DBUG_ENTER("Rotate_log_event::do_update_pos"); #ifndef DBUG_OFF char buf[32]; @@ -6006,11 +6100,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 != 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' " @@ -6019,18 +6118,18 @@ 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); - rpl_global_gtid_slave_state.record_and_update_gtid(thd, rli); + rpl_global_gtid_slave_state.record_and_update_gtid(thd, rgi); 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). @@ -6042,7 +6141,7 @@ int Rotate_log_event::do_update_pos(Relay_log_info *rli) thd->variables.auto_increment_offset= 1; } else - rli->inc_event_relay_log_pos(); + rgi->inc_event_relay_log_pos(); DBUG_RETURN(0); @@ -6050,9 +6149,9 @@ int Rotate_log_event::do_update_pos(Relay_log_info *rli) 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: @@ -6155,7 +6254,7 @@ bool Binlog_checkpoint_log_event::write(IO_CACHE *file) 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) + : 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]; @@ -6169,6 +6268,16 @@ Gtid_log_event::Gtid_log_event(const char *buf, uint event_len, 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); + } } @@ -6176,10 +6285,11 @@ Gtid_log_event::Gtid_log_event(const char *buf, uint event_len, 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) + uint16 flags_arg, bool is_transactional, + uint64 commit_id_arg) : Log_event(thd_arg, flags_arg, is_transactional), - seq_no(seq_no_arg), domain_id(domain_id_arg), - flags2(standalone ? FL_STANDALONE : 0) + 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; } @@ -6193,7 +6303,7 @@ bool Gtid_log_event::peek(const char *event_start, size_t event_len, uint8 checksum_alg, uint32 *domain_id, uint32 *server_id, uint64 *seq_no, - uchar *flags2) + uchar *flags2, const Format_description_log_event *fdev) { const char *p; @@ -6208,10 +6318,10 @@ Gtid_log_event::peek(const char *event_start, size_t event_len, DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || checksum_alg == BINLOG_CHECKSUM_ALG_OFF); - if (event_len < LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN) + if (event_len < (uint32)fdev->common_header_len + GTID_HEADER_LEN) return true; *server_id= uint4korr(event_start + SERVER_ID_OFFSET); - p= event_start + LOG_EVENT_HEADER_LEN; + p= event_start + fdev->common_header_len; *seq_no= uint8korr(p); p+= 8; *domain_id= uint4korr(p); @@ -6224,13 +6334,24 @@ Gtid_log_event::peek(const char *event_start, size_t event_len, bool Gtid_log_event::write(IO_CACHE *file) { - uchar buf[GTID_HEADER_LEN]; + uchar buf[GTID_HEADER_LEN+2]; + size_t write_len; + int8store(buf, seq_no); int4store(buf+8, domain_id); buf[12]= flags2; - bzero(buf+13, GTID_HEADER_LEN-13); - return write_header(file, GTID_HEADER_LEN) || - wrapper_my_b_safe_write(file, buf, GTID_HEADER_LEN) || + 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(file, write_len) || + wrapper_my_b_safe_write(file, buf, write_len) || write_footer(file); } @@ -6269,7 +6390,7 @@ Gtid_log_event::make_compatible_event(String *packet, bool *need_dummy_event, void Gtid_log_event::pack_info(THD *thd, Protocol *protocol) { - char buf[6+5+10+1+10+1+20+1]; + 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); @@ -6277,6 +6398,11 @@ Gtid_log_event::pack_info(THD *thd, Protocol *protocol) 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); } @@ -6284,7 +6410,7 @@ Gtid_log_event::pack_info(THD *thd, Protocol *protocol) static char gtid_begin_string[] = "BEGIN"; int -Gtid_log_event::do_apply_event(Relay_log_info const *rli) +Gtid_log_event::do_apply_event(rpl_group_info *rgi) { thd->variables.server_id= this->server_id; thd->variables.gtid_domain_id= this->domain_id; @@ -6325,16 +6451,17 @@ Gtid_log_event::do_apply_event(Relay_log_info const *rli) int -Gtid_log_event::do_update_pos(Relay_log_info *rli) +Gtid_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 -Gtid_log_event::do_shall_skip(Relay_log_info *rli) +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. @@ -6346,10 +6473,13 @@ Gtid_log_event::do_shall_skip(Relay_log_info *rli) if (rli->slave_skip_counter > 0) { if (!(flags2 & FL_STANDALONE)) + { thd->variables.option_bits|= OPTION_BEGIN; - return Log_event::continue_group(rli); + DBUG_ASSERT(rgi->rli->get_flag(Relay_log_info::IN_TRANSACTION)); + } + return Log_event::continue_group(rgi); } - return Log_event::do_shall_skip(rli); + return Log_event::do_shall_skip(rgi); } @@ -6363,12 +6493,20 @@ 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\n", domain_id, server_id, buf); + if (flags2 & FL_GROUP_COMMIT_ID) + { + longlong10_to_str(commit_id, buf2, 10); + my_b_printf(&cache, "\tGTID %u-%u-%s cid=%s\n", + domain_id, server_id, buf, buf2); + } + else + my_b_printf(&cache, "\tGTID %u-%u-%s\n", domain_id, server_id, buf); if (!print_event_info->domain_id_printed || print_event_info->domain_id != domain_id) @@ -6402,7 +6540,7 @@ Gtid_log_event::print(FILE *file, PRINT_EVENT_INFO *print_event_info) 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) + : Log_event(buf, description_event), count(0), list(0), sub_id_list(0) { uint32 i; uint32 val; @@ -6431,6 +6569,31 @@ Gtid_list_log_event::Gtid_list_log_event(const char *buf, uint event_len, 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 } @@ -6438,7 +6601,7 @@ Gtid_list_log_event::Gtid_list_log_event(const char *buf, uint event_len, 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) + : 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. */ @@ -6449,6 +6612,47 @@ Gtid_list_log_event::Gtid_list_log_event(rpl_binlog_state *gtid_set, } +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) @@ -6498,9 +6702,23 @@ Gtid_list_log_event::write(IO_CACHE *file) int -Gtid_list_log_event::do_apply_event(Relay_log_info const *rli) +Gtid_list_log_event::do_apply_event(rpl_group_info *rgi) { - int ret= Log_event::do_apply_event(rli); + Relay_log_info const *rli= 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], + false, false))) + return ret; + rpl_global_gtid_slave_state.update_state_hash(sub_id_list[i], &list[i]); + } + } + ret= Log_event::do_apply_event(rgi); if (rli->until_condition == Relay_log_info::UNTIL_GTID && (gl_flags & FLAG_UNTIL_REACHED)) { @@ -6570,7 +6788,8 @@ Gtid_list_log_event::print(FILE *file, PRINT_EVENT_INFO *print_event_info) bool Gtid_list_log_event::peek(const char *event_start, uint32 event_len, uint8 checksum_alg, - rpl_gtid **out_gtid_list, uint32 *out_list_len) + rpl_gtid **out_gtid_list, uint32 *out_list_len, + const Format_description_log_event *fdev) { const char *p; uint32 count_field, count; @@ -6587,13 +6806,13 @@ Gtid_list_log_event::peek(const char *event_start, uint32 event_len, DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || checksum_alg == BINLOG_CHECKSUM_ALG_OFF); - if (event_len < LOG_EVENT_HEADER_LEN + GTID_LIST_HEADER_LEN) + if (event_len < (uint32)fdev->common_header_len + GTID_LIST_HEADER_LEN) return true; - p= event_start + LOG_EVENT_HEADER_LEN; + p= event_start + fdev->common_header_len; count_field= uint4korr(p); p+= 4; count= count_field & ((1<<28)-1); - if (event_len < LOG_EVENT_HEADER_LEN + GTID_LIST_HEADER_LEN + + 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), @@ -6727,38 +6946,38 @@ 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) { - /* - 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); + DBUG_ENTER("Intvar_log_event::do_apply_event"); + if (rgi->deferred_events_collecting) + { + DBUG_PRINT("info",("deferring event")); + DBUG_RETURN(rgi->deferred_events->add(this)); + } switch (type) { case LAST_INSERT_ID_EVENT: thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 1; - thd->first_successful_insert_id_in_prev_stmt= val; + thd->first_successful_insert_id_in_prev_stmt_for_binlog= + thd->first_successful_insert_id_in_prev_stmt= val; + DBUG_PRINT("info",("last_insert_id_event: %ld", (long) val)); break; case INSERT_ID_EVENT: thd->force_one_auto_inc_interval(val); break; } - return 0; + 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 @@ -6768,7 +6987,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 @@ -6836,31 +7055,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 @@ -6870,7 +7083,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); } /** @@ -6884,14 +7097,14 @@ 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); return res; } @@ -6966,23 +7179,24 @@ 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; + Relay_log_info const *rli= rgi->rli; /* Record any GTID in the same transaction, so slave state is transactionally consistent. */ - if ((sub_id= rli->gtid_sub_id)) + if ((sub_id= rgi->gtid_sub_id)) { /* Clear the GTID from the RLI so we don't accidentally reuse it. */ - const_cast<Relay_log_info*>(rli)->gtid_sub_id= 0; + rgi->gtid_sub_id= 0; - gtid= rli->current_gtid; + gtid= rgi->current_gtid; err= rpl_global_gtid_slave_state.record_gtid(thd, >id, sub_id, true, false); if (err) { @@ -7022,10 +7236,12 @@ 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) { + if (rgi->rli->slave_skip_counter > 0) + { + DBUG_ASSERT(!rgi->rli->get_flag(Relay_log_info::IN_TRANSACTION)); thd->variables.option_bits&= ~OPTION_BEGIN; DBUG_RETURN(Log_event::EVENT_SKIP_COUNT); } @@ -7043,7 +7259,7 @@ Xid_log_event::do_shall_skip(Relay_log_info *rli) } } #endif - DBUG_RETURN(Log_event::do_shall_skip(rli)); + DBUG_RETURN(Log_event::do_shall_skip(rgi)); } #endif /* !MYSQL_CLIENT */ @@ -7452,17 +7668,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()) { @@ -7478,12 +7694,6 @@ 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(); @@ -7548,14 +7758,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 @@ -7565,7 +7775,7 @@ 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 */ @@ -7724,7 +7934,7 @@ Slave_log_event::Slave_log_event(const char* buf, #ifndef MYSQL_CLIENT -int Slave_log_event::do_apply_event(Relay_log_info const *rli) +int Slave_log_event::do_apply_event(rpl_group_info *rgi) { if (mysql_bin_log.is_open()) return mysql_bin_log.write(this); @@ -7768,8 +7978,11 @@ 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) { + 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. @@ -7777,15 +7990,15 @@ 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) { - rpl_global_gtid_slave_state.record_and_update_gtid(thd, rli); - rli->inc_group_relay_log_pos(0); + rpl_global_gtid_slave_state.record_and_update_gtid(thd, rgi); + rli->inc_group_relay_log_pos(0, rgi); flush_relay_log_info(rli); } - return 0; + DBUG_RETURN(0); } #endif /* !MYSQL_CLIENT */ @@ -7999,13 +8212,14 @@ 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 fname_buf[FN_REFLEN]; char *ext; int fd = -1; IO_CACHE 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)); @@ -8186,11 +8400,12 @@ 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 fname[FN_REFLEN]; int fd; int error = 1; + Relay_log_info const *rli= rgi->rli; DBUG_ENTER("Append_block_log_event::do_apply_event"); THD_STAGE_INFO(thd, stage_making_temp_file_append_before_load_data); @@ -8203,7 +8418,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, 0); + mysql_reset_thd_for_next_command(thd); /* 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, @@ -8342,9 +8557,10 @@ 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]; + 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)); @@ -8441,7 +8657,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; @@ -8449,6 +8665,7 @@ 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", &rli->mi->cmp_connection_name); @@ -8483,8 +8700,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 @@ -8565,13 +8781,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 @@ -8713,13 +8929,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)); @@ -8756,7 +8973,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; @@ -9120,8 +9337,9 @@ 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) +int Rows_log_event::do_apply_event(rpl_group_info *rgi) { + Relay_log_info const *rli= rgi->rli; DBUG_ENTER("Rows_log_event::do_apply_event(Relay_log_info*)"); int error= 0; /* @@ -9138,7 +9356,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); } @@ -9148,7 +9366,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 @@ -9167,8 +9385,9 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) 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, 0); + mysql_reset_thd_for_next_command(thd); /* The current statement is just about to begin and has not yet modified anything. Note, all.modified is reset @@ -9199,7 +9418,8 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS; /* 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 (open_and_lock_tables(thd, rgi->tables_to_lock, FALSE, 0)) { uint actual_error= thd->get_stmt_da()->sql_errno(); #ifdef WITH_WSREP @@ -9228,7 +9448,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) "unexpected success or fatal error")); 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); } @@ -9242,7 +9462,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 @@ -9256,8 +9476,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. */ - RPL_TABLE_LIST *ptr= rli->tables_to_lock; - for (uint i= 0 ; ptr && (i < rli->tables_to_lock_count); + RPL_TABLE_LIST *ptr= rgi->tables_to_lock; + for (uint i= 0 ; ptr && (i < rgi->tables_to_lock_count); ptr= static_cast<RPL_TABLE_LIST*>(ptr->next_global), i++) { DBUG_ASSERT(ptr->m_tabledef_valid); @@ -9273,7 +9493,7 @@ 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); + rgi->slave_close_thread_tables(thd); DBUG_RETURN(ERR_BAD_TABLE_DEF); } DBUG_PRINT("debug", ("Table: %s.%s is compatible with master" @@ -9298,18 +9518,18 @@ 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++) - const_cast<Relay_log_info*>(rli)->m_table_map.set_table(ptr->table_id, ptr->table); + TABLE_LIST *ptr= rgi->tables_to_lock; + for (uint i=0 ; ptr && (i < rgi->tables_to_lock_count); ptr= ptr->next_global, i++) + 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); DBUG_PRINT("debug", ("m_table: 0x%lx, m_table_id: %lu", (ulong) m_table, m_table_id)); @@ -9332,17 +9552,6 @@ 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)) set_flags(COMPLETE_ROWS_F); @@ -9382,7 +9591,7 @@ 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) { @@ -9391,7 +9600,7 @@ int 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); if (error) DBUG_PRINT("info", ("error: %s", HA_ERR(error))); @@ -9431,7 +9640,7 @@ 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); + error= unpack_current_row(rgi); // at this moment m_curr_row_end should be set DBUG_ASSERT(error || m_curr_row_end != NULL); @@ -9492,7 +9701,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) DBUG_RETURN(error); } - if (get_flags(STMT_END_F) && (error= rows_event_stmt_cleanup(rli, thd))) + 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, @@ -9502,17 +9711,17 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) } 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); } /** @@ -9526,9 +9735,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 @@ -9580,9 +9791,16 @@ 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; + + rgi->cleanup_context(thd, 0); } - return error; + DBUG_RETURN(error); } /** @@ -9596,8 +9814,9 @@ 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) { + Relay_log_info *rli= rgi->rli; DBUG_ENTER("Rows_log_event::do_update_pos"); int error= 0; @@ -9611,7 +9830,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, thd); + rli->stmt_done(log_pos, when, 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 @@ -9622,7 +9841,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); @@ -9834,7 +10053,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(); @@ -9844,18 +10063,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 @@ -10317,24 +10536,25 @@ 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; + Relay_log_info *rli= rgi->rli; #ifdef WITH_WSREP - if ((rli->sql_thd->slave_thread /* filtering is for slave only */ || - (WSREP(rli->sql_thd) && rli->sql_thd->wsrep_applier)) && + if ((rgi->thd->slave_thread /* filtering is for slave only */ || + (WSREP(rgi->thd) && rgi->thd->wsrep_applier)) && #else - if (rli->sql_thd->slave_thread /* filtering is for slave only */ && + if (rgi->thd->slave_thread /* filtering is for slave only */ && #endif /* WITH_WSREP */ (!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) @@ -10357,15 +10577,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; 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()); @@ -10378,7 +10598,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* call from mysql_client_binlog_statement() will not set rli->mi */ - filter= rli->sql_thd->slave_thread ? rli->mi->rpl_filter : global_rpl_filter; + filter= rgi->thd->slave_thread ? rli->mi->rpl_filter : global_rpl_filter; strmov(db_mem, filter->get_rewrite_db(m_dbnam, &dummy_len)); strmov(tname_mem, m_tblnam); @@ -10390,7 +10610,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) 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); + 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); @@ -10416,9 +10636,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_* @@ -10466,18 +10686,18 @@ 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; } @@ -10770,7 +10990,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"); @@ -10785,7 +11005,7 @@ Rows_log_event::write_row(const Relay_log_info *const rli, table->file->ht->db_type != DB_TYPE_NDBCLUSTER); /* unpack row into table->record[0] */ - if ((error= unpack_current_row(rli))) + if ((error= unpack_current_row(rgi))) DBUG_RETURN(error); if (m_curr_row == m_rows_buf) @@ -10902,7 +11122,7 @@ 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 @@ -10968,7 +11188,7 @@ Rows_log_event::write_row(const Relay_log_info *const rli, #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); #ifdef WITH_WSREP @@ -10983,7 +11203,7 @@ Write_rows_log_event::do_exec_row(const Relay_log_info *const rli) thd_proc_info(thd,"Write_rows_log_event::write_row()") : NULL; #endif /* WSREP_PROC_INFO */ #endif /* WITH_WSREP */ - int error= write_row(rli, slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT); + int error= write_row(rgi, slave_exec_mode == SLAVE_EXEC_MODE_IDEMPOTENT); #ifdef WITH_WSREP if (WSREP(thd)) thd_proc_info(thd, tmp); @@ -11231,13 +11451,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);); @@ -11246,7 +11466,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"; @@ -11292,7 +11512,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"); @@ -11310,7 +11530,7 @@ 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")); @@ -11581,7 +11801,7 @@ 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); + is_index_scan, rgi); table->default_column_bitmaps(); DBUG_RETURN(error); } @@ -11649,7 +11869,7 @@ 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; DBUG_ASSERT(m_table != NULL); @@ -11666,7 +11886,7 @@ int Delete_rows_log_event::do_exec_row(const Relay_log_info *const rli) thd_proc_info(thd,"Delete_rows_log_event::find_row()") : NULL; #endif /* WSREP_PROC_INFO */ #endif /* WITH_WSREP */ - if (!(error= find_row(rli))) + if (!(error= find_row(rgi))) { /* Delete the record found, located in record[0] @@ -11800,7 +12020,7 @@ 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) { DBUG_ASSERT(m_table != NULL); @@ -11816,7 +12036,7 @@ Update_rows_log_event::do_exec_row(const Relay_log_info *const rli) thd_proc_info(thd,"Update_rows_log_event::find_row()") : NULL; #endif /* WSREP_PROC_INFO */ #endif /* WITH_WSREP */ - int error= find_row(rli); + int error= find_row(rgi); if (error) { /* @@ -11824,7 +12044,7 @@ Update_rows_log_event::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; } @@ -11854,7 +12074,7 @@ Update_rows_log_event::do_exec_row(const Relay_log_info *const rli) #endif /* WSREP_PROC_INFO */ #endif /* WITH_WSREP */ /* this also updates m_curr_row_end */ - if ((error= unpack_current_row(rli))) + if ((error= unpack_current_row(rgi))) goto err; /* @@ -12032,8 +12252,9 @@ 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), @@ -12136,11 +12357,21 @@ 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 (opt_slave_parallel_threads == 0) + { + *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 } diff --git a/sql/log_event.h b/sql/log_event.h index b73c0e71f77..138ed2c6926 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -41,7 +41,6 @@ #include "rpl_utility.h" #include "hash.h" #include "rpl_tblmap.h" -#include "rpl_tblmap.cc" #endif #ifdef MYSQL_SERVER @@ -1253,6 +1252,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; } @@ -1317,9 +1317,9 @@ 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); + return do_apply_event(rgi); } @@ -1331,9 +1331,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); } /** @@ -1342,9 +1342,9 @@ 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); } @@ -1352,6 +1352,7 @@ public: 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) { @@ -1375,7 +1376,32 @@ public: 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: + return false; + default: + return true; + } + } + protected: /** @@ -1388,14 +1414,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. @@ -1412,7 +1438,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 */ } @@ -1441,7 +1467,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); /** @@ -1473,7 +1499,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 }; @@ -1965,11 +1991,11 @@ 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); + virtual int do_update_pos(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, @@ -1997,6 +2023,9 @@ 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"); } }; @@ -2083,7 +2112,7 @@ public: 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 }; @@ -2396,12 +2425,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 }; @@ -2469,6 +2498,8 @@ 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); #endif @@ -2480,8 +2511,8 @@ 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 @@ -2576,9 +2607,9 @@ public: 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 }; @@ -2652,12 +2683,13 @@ Intvar_log_event(THD* thd_arg,uchar type_arg, ulonglong val_arg, bool write(IO_CACHE* file); #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 }; @@ -2731,12 +2763,13 @@ class Rand_log_event: public Log_event bool write(IO_CACHE* file); #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 }; @@ -2783,8 +2816,8 @@ class Xid_log_event: public Log_event 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 }; @@ -2852,12 +2885,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 }; @@ -2890,8 +2924,8 @@ 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 @@ -2984,6 +3018,8 @@ 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 @@ -2992,8 +3028,8 @@ 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); #endif }; @@ -3085,6 +3121,7 @@ class Gtid_log_event: public Log_event { public: uint64 seq_no; + uint64 commit_id; uint32 domain_id; uchar flags2; @@ -3092,15 +3129,20 @@ public: /* 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; #ifdef MYSQL_SERVER Gtid_log_event(THD *thd_arg, uint64 seq_no, uint32 domain_id, bool standalone, - uint16 flags, bool is_transactional); + uint16 flags, bool is_transactional, uint64 commit_id); #ifdef HAVE_REPLICATION void pack_info(THD *thd, Protocol *protocol); - 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 #else void print(FILE *file, PRINT_EVENT_INFO *print_event_info); @@ -3109,7 +3151,10 @@ public: 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; } + 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(IO_CACHE *file); @@ -3118,7 +3163,7 @@ public: static bool peek(const char *event_start, size_t event_len, uint8 checksum_alg, uint32 *domain_id, uint32 *server_id, uint64 *seq_no, - uchar *flags2); + uchar *flags2, const Format_description_log_event *fdev); #endif }; @@ -3197,12 +3242,15 @@ 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(THD *thd, Protocol *protocol); #endif @@ -3211,7 +3259,7 @@ public: #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); } + ~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() { /* @@ -3225,11 +3273,12 @@ public: #if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION) bool to_packet(String *packet); bool write(IO_CACHE *file); - virtual int do_apply_event(Relay_log_info const *rli); + virtual int do_apply_event(rpl_group_info *rgi); #endif static bool peek(const char *event_start, uint32 event_len, uint8 checksum_alg, - rpl_gtid **out_gtid_list, uint32 *out_list_len); + rpl_gtid **out_gtid_list, uint32 *out_list_len, + const Format_description_log_event *fdev); }; @@ -3304,7 +3353,7 @@ public: 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 }; @@ -3359,7 +3408,7 @@ public: 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 }; @@ -3400,7 +3449,7 @@ public: 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 }; @@ -3440,7 +3489,7 @@ public: 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 }; @@ -3473,7 +3522,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 }; @@ -3539,7 +3588,7 @@ public: 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 }; @@ -3595,6 +3644,7 @@ 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*); @@ -3611,9 +3661,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: @@ -4006,6 +4056,7 @@ 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 @@ -4026,9 +4077,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 @@ -4171,6 +4222,7 @@ 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 */ @@ -4232,16 +4284,16 @@ protected: uint m_key_nr; /* Key number */ 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] - 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); - int const result= ::unpack_row(rli, m_table, m_width, m_curr_row, + int const result= ::unpack_row(rgi, m_table, m_width, m_curr_row, m_rows_end, &m_cols, &m_curr_row_end, &m_master_reclength); if (m_curr_row_end > m_rows_end) @@ -4254,9 +4306,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. @@ -4307,7 +4359,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; @@ -4363,7 +4415,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 }; @@ -4437,7 +4489,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) */ }; @@ -4502,7 +4554,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 }; @@ -4588,7 +4640,7 @@ 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); @@ -4674,16 +4726,6 @@ bool event_checksum_test(uchar *buf, ulong event_len, uint8 alg); uint8 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 6623d7655d7..211768150af 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -36,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 @@ -57,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); } @@ -67,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 @@ -86,8 +87,9 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info 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, 0); + mysql_reset_thd_for_next_command(ev_thd); /* This is a row injection, so we flag the "statement" as @@ -97,7 +99,7 @@ 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->get_stmt_da()->sql_errno(); if (ev_thd->is_slave_error || ev_thd->is_fatal_error) @@ -112,7 +114,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info "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); } @@ -125,8 +127,8 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info */ { - RPL_TABLE_LIST *ptr= rli->tables_to_lock; - for (uint i= 0 ; ptr&& (i< rli->tables_to_lock_count); + RPL_TABLE_LIST *ptr= rgi->tables_to_lock; + for (uint i= 0 ; ptr&& (i< rgi->tables_to_lock_count); ptr= static_cast<RPL_TABLE_LIST*>(ptr->next_global), i++) { DBUG_ASSERT(ptr->m_tabledef_valid); @@ -135,7 +137,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info 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" @@ -160,15 +162,15 @@ 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++) - const_cast<Relay_log_info*>(rli)->m_table_map.set_table(ptr->table_id, ptr->table); + TABLE_LIST *ptr= rgi->tables_to_lock; + for (uint i=0; ptr && (i < rgi->tables_to_lock_count); ptr= ptr->next_global, i++) + 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) { @@ -204,22 +206,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 @@ -279,7 +270,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); } @@ -952,7 +943,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) @@ -961,7 +952,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, @@ -1036,7 +1027,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) @@ -1049,7 +1040,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, @@ -1133,7 +1124,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) @@ -1147,14 +1138,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, @@ -1450,10 +1441,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 @@ -1469,7 +1461,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); } @@ -1479,7 +1471,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 @@ -1497,8 +1489,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) { @@ -1520,7 +1512,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) "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); } @@ -1533,8 +1525,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ { - RPL_TABLE_LIST *ptr= rli->tables_to_lock; - for (uint i= 0 ; ptr&& (i< rli->tables_to_lock_count); + RPL_TABLE_LIST *ptr= rgi->tables_to_lock; + for (uint i= 0 ; ptr&& (i< rgi->tables_to_lock_count); ptr= static_cast<RPL_TABLE_LIST*>(ptr->next_global), i++) { TABLE *conv_table; @@ -1542,7 +1534,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) 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; @@ -1564,18 +1556,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) { @@ -1611,17 +1603,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); @@ -1655,7 +1636,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); @@ -1694,7 +1675,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); @@ -1731,7 +1712,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); } @@ -1760,7 +1741,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)) @@ -1810,7 +1791,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); @@ -1818,22 +1799,23 @@ 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) { + Relay_log_info *rli= rgi->rli; DBUG_ENTER("Old_rows_log_event::do_update_pos"); int error= 0; @@ -1847,7 +1829,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, thd); + rli->stmt_done(log_pos, when, 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 @@ -1858,7 +1840,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); @@ -1995,8 +1977,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); @@ -2013,7 +1994,7 @@ 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? + error= unpack_current_row(rgi); // TODO: how to handle errors? #ifndef DBUG_OFF DBUG_DUMP("record[0]", table->record[0], table->s->reclength); @@ -2120,7 +2101,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 @@ -2215,7 +2196,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"); @@ -2228,7 +2209,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")); @@ -2600,10 +2581,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; @@ -2702,12 +2683,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] @@ -2801,11 +2782,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) { /* @@ -2813,7 +2794,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; } @@ -2831,7 +2812,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 0034bb9d142..ef81739a543 100644 --- a/sql/log_event_old.h +++ b/sql/log_event_old.h @@ -145,6 +145,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 +196,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); - int const result= ::unpack_row(rli, m_table, m_width, m_curr_row, + int const result= ::unpack_row(rgi, m_table, m_width, m_curr_row, m_rows_end, &m_cols, &m_curr_row_end, &m_master_reclength); ASSERT_OR_RETURN_ERROR(m_curr_row_end <= m_rows_end, HA_ERR_CORRUPT_EVENT); @@ -214,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. @@ -267,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 **********/ @@ -275,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. @@ -324,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; @@ -387,7 +388,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 **********/ @@ -403,13 +404,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); @@ -463,7 +464,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 **********/ @@ -481,13 +482,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) */ @@ -538,7 +539,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 **********/ @@ -556,13 +557,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 92a2d1bf4f6..e8faf79a047 100644 --- a/sql/log_slow.h +++ b/sql/log_slow.h @@ -18,6 +18,7 @@ #define LOG_SLOW_VERBOSITY_INIT 0 #define LOG_SLOW_VERBOSITY_INNODB 1 << 0 #define LOG_SLOW_VERBOSITY_QUERY_PLAN 1 << 1 +#define LOG_SLOW_VERBOSITY_EXPLAIN 1 << 2 #define QPLAN_INIT QPLAN_QC_NO diff --git a/sql/mdl.cc b/sql/mdl.cc index a13aeb7904d..c74a3e964cb 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -1440,7 +1440,7 @@ void MDL_lock::Ticket_list::add_ticket(MDL_ticket *ticket) DBUG_ASSERT(ticket->get_lock()); #ifdef WITH_WSREP if ((this == &(ticket->get_lock()->m_waiting)) && - wsrep_thd_is_brute_force((void *)(ticket->get_ctx()->get_thd()))) + wsrep_thd_is_brute_force((void *)(ticket->get_ctx()->wsrep_get_thd()))) { Ticket_iterator itw(ticket->get_lock()->m_waiting); Ticket_iterator itg(ticket->get_lock()->m_granted); @@ -1451,11 +1451,11 @@ void MDL_lock::Ticket_list::add_ticket(MDL_ticket *ticket) while ((waiting= itw++) && !added) { - if (!wsrep_thd_is_brute_force((void *)(waiting->get_ctx()->get_thd()))) + if (!wsrep_thd_is_brute_force((void *)(waiting->get_ctx()->wsrep_get_thd()))) { WSREP_DEBUG("MDL add_ticket inserted before: %lu %s", - wsrep_thd_thread_id(waiting->get_ctx()->get_thd()), - wsrep_thd_query(waiting->get_ctx()->get_thd())); + wsrep_thd_thread_id(waiting->get_ctx()->wsrep_get_thd()), + wsrep_thd_query(waiting->get_ctx()->wsrep_get_thd())); m_list.insert_after(prev, ticket); added= true; } @@ -1852,12 +1852,12 @@ MDL_lock::can_grant_lock(enum_mdl_type type_arg, ticket->is_incompatible_when_granted(type_arg)) #ifdef WITH_WSREP { - if (wsrep_thd_is_brute_force((void *)(requestor_ctx->get_thd())) && + if (wsrep_thd_is_brute_force((void *)(requestor_ctx->wsrep_get_thd())) && key.mdl_namespace() == MDL_key::GLOBAL) { WSREP_DEBUG("global lock granted for BF: %lu %s", - wsrep_thd_thread_id(requestor_ctx->get_thd()), - wsrep_thd_query(requestor_ctx->get_thd())); + wsrep_thd_thread_id(requestor_ctx->wsrep_get_thd()), + wsrep_thd_query(requestor_ctx->wsrep_get_thd())); can_grant = true; } else if (!wsrep_grant_mdl_exception(requestor_ctx, ticket)) @@ -1893,12 +1893,12 @@ MDL_lock::can_grant_lock(enum_mdl_type type_arg, #ifdef WITH_WSREP else { - if (wsrep_thd_is_brute_force((void *)(requestor_ctx->get_thd())) && + if (wsrep_thd_is_brute_force((void *)(requestor_ctx->wsrep_get_thd())) && key.mdl_namespace() == MDL_key::GLOBAL) { WSREP_DEBUG("global lock granted for BF (waiting queue): %lu %s", - wsrep_thd_thread_id(requestor_ctx->get_thd()), - wsrep_thd_query(requestor_ctx->get_thd())); + wsrep_thd_thread_id(requestor_ctx->wsrep_get_thd()), + wsrep_thd_query(requestor_ctx->wsrep_get_thd())); can_grant = true; } } diff --git a/sql/mdl.h b/sql/mdl.h index 8d22eff1eff..75132962f8f 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -916,9 +916,7 @@ private: */ MDL_wait_for_subgraph *m_waiting_for; private: -#ifndef WITH_WSREP THD *get_thd() const { return m_owner->get_thd(); } -#endif MDL_ticket *find_ticket(MDL_request *mdl_req, enum_mdl_duration *duration); void release_locks_stored_before(enum_mdl_duration duration, MDL_ticket *sentinel); @@ -928,7 +926,7 @@ private: public: #ifdef WITH_WSREP - THD *get_thd() const { return m_owner->get_thd(); } + THD *wsrep_get_thd() const { return get_thd(); } #endif void find_deadlock(); diff --git a/sql/multi_range_read.cc b/sql/multi_range_read.cc index e42ea9ec452..03023927a1d 100644 --- a/sql/multi_range_read.cc +++ b/sql/multi_range_read.cc @@ -1494,10 +1494,10 @@ ha_rows DsMrr_impl::dsmrr_info_const(uint keyno, RANGE_SEQ_IF *seq, @retval FALSE No */ -bool key_uses_partial_cols(TABLE *table, uint keyno) +bool key_uses_partial_cols(TABLE_SHARE *share, uint keyno) { - KEY_PART_INFO *kp= table->key_info[keyno].key_part; - KEY_PART_INFO *kp_end= kp + table->key_info[keyno].user_defined_key_parts; + KEY_PART_INFO *kp= share->key_info[keyno].key_part; + 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)) @@ -1518,10 +1518,11 @@ bool key_uses_partial_cols(TABLE *table, uint keyno) @retval FALSE Otherwise */ -bool DsMrr_impl::check_cpk_scan(THD *thd, uint keyno, uint mrr_flags) +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 == table->s->primary_key && + keyno == share->primary_key && primary_file->primary_key_is_clustered() && optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_SORT_KEYS)); } @@ -1557,14 +1558,15 @@ bool DsMrr_impl::choose_mrr_impl(uint keyno, ha_rows rows, uint *flags, Cost_estimate dsmrr_cost; bool res; THD *thd= current_thd; + TABLE_SHARE *share= primary_file->get_table_share(); - bool doing_cpk_scan= check_cpk_scan(thd, keyno, *flags); - bool using_cpk= test(keyno == table->s->primary_key && + 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()); *flags &= ~HA_MRR_IMPLEMENTATION_FLAGS; if (!optimizer_flag(thd, OPTIMIZER_SWITCH_MRR) || *flags & HA_MRR_INDEX_ONLY || - (using_cpk && !doing_cpk_scan) || key_uses_partial_cols(table, keyno)) + (using_cpk && !doing_cpk_scan) || key_uses_partial_cols(share, keyno)) { /* Use the default implementation */ *flags |= HA_MRR_USE_DEFAULT_IMPL; @@ -1572,7 +1574,7 @@ bool DsMrr_impl::choose_mrr_impl(uint keyno, ha_rows rows, uint *flags, return TRUE; } - uint add_len= table->key_info[keyno].key_length + primary_file->ref_length; + uint add_len= share->key_info[keyno].key_length + primary_file->ref_length; *bufsz -= add_len; if (get_disk_sweep_mrr_cost(keyno, rows, *flags, bufsz, &dsmrr_cost)) return TRUE; diff --git a/sql/multi_range_read.h b/sql/multi_range_read.h index 387ae9791bc..3b5375293de 100644 --- a/sql/multi_range_read.h +++ b/sql/multi_range_read.h @@ -627,7 +627,7 @@ private: Cost_estimate *cost); bool get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags, uint *buffer_size, Cost_estimate *cost); - bool check_cpk_scan(THD *thd, uint keyno, uint mrr_flags); + 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 index 755b3890433..17660688be0 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -70,6 +70,7 @@ void Apc_target::enable() void Apc_target::disable() { bool process= FALSE; + DBUG_ASSERT(enabled); mysql_mutex_lock(LOCK_thd_data_ptr); if (!(--enabled)) process= TRUE; diff --git a/sql/my_apc.h b/sql/my_apc.h index c84074b2da5..a12db5093a4 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -64,6 +64,8 @@ public: { return test(apc_calls); } + + inline bool is_enabled() { return enabled; } /* Functor class for calls you can schedule */ class Apc_call diff --git a/sql/mysqld.cc b/sql/mysqld.cc index ecc29fe6303..517e2a6d110 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -487,10 +487,11 @@ uint lower_case_table_names; ulong tc_heuristic_recover= 0; int32 thread_count; int32 thread_running; +int32 slave_open_temp_tables; ulong thread_created; ulong back_log, connect_timeout, concurrency, server_id; ulong what_to_log; -ulong slow_launch_time, slave_open_temp_tables; +ulong slow_launch_time; ulong open_files_limit, max_binlog_size; ulong slave_trans_retries; uint slave_net_timeout; @@ -510,6 +511,7 @@ my_atomic_rwlock_t global_query_id_lock; my_atomic_rwlock_t thread_running_lock; my_atomic_rwlock_t thread_count_lock; my_atomic_rwlock_t statistics_lock; +my_atomic_rwlock_t slave_executed_entries_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; @@ -562,6 +564,11 @@ ulong rpl_recovery_rank=0; */ ulong stored_program_cache_size= 0; +ulong opt_slave_parallel_threads= 0; +ulong opt_binlog_commit_wait_count= 0; +ulong opt_binlog_commit_wait_usec= 0; +ulong opt_slave_parallel_max_queued= 131072; + const double log_10[] = { 1e000, 1e001, 1e002, 1e003, 1e004, 1e005, 1e006, 1e007, 1e008, 1e009, 1e010, 1e011, 1e012, 1e013, 1e014, 1e015, 1e016, 1e017, 1e018, 1e019, @@ -616,7 +623,6 @@ key_map key_map_full(0); // Will be initialized later DATE_TIME_FORMAT global_date_format, global_datetime_format, global_time_format; Time_zone *default_tz; -char *mysql_data_home= const_cast<char*>("."); const char *mysql_real_data_home_ptr= mysql_real_data_home; char server_version[SERVER_VERSION_LENGTH]; char *mysqld_unix_port, *opt_mysql_tmpdir; @@ -879,7 +885,7 @@ PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list, key_master_info_data_lock, key_master_info_run_lock, key_master_info_sleep_lock, key_mutex_slave_reporting_capability_err_lock, key_relay_log_info_data_lock, - key_relay_log_info_sleep_lock, + key_rpl_group_info_sleep_lock, key_relay_log_info_log_space_lock, key_relay_log_info_run_lock, key_structure_guard_mutex, key_TABLE_SHARE_LOCK_ha_data, key_LOCK_error_messages, key_LOG_INFO_lock, @@ -892,12 +898,13 @@ PSI_mutex_key key_LOCK_wsrep_rollback, key_LOCK_wsrep_thd, key_LOCK_wsrep_slave_threads, key_LOCK_wsrep_desync; #endif PSI_mutex_key key_RELAYLOG_LOCK_index; -PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state; +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_rpl_gtid_state; @@ -945,6 +952,7 @@ static PSI_mutex_info all_server_mutexes[]= { &key_LOCK_global_index_stats, "LOCK_global_index_stats", PSI_FLAG_GLOBAL}, { &key_LOCK_wakeup_ready, "THD::LOCK_wakeup_ready", 0}, { &key_LOCK_rpl_gtid_state, "LOCK_rpl_gtid_state", PSI_FLAG_GLOBAL}, + { &key_LOCK_wait_commit, "wait_for_commit::LOCK_wait_commit", 0}, { &key_LOCK_thd_data, "THD::LOCK_thd_data", 0}, { &key_LOCK_user_conn, "LOCK_user_conn", PSI_FLAG_GLOBAL}, { &key_LOCK_uuid_short_generator, "LOCK_uuid_short_generator", PSI_FLAG_GLOBAL}, @@ -956,7 +964,7 @@ static PSI_mutex_info all_server_mutexes[]= { &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}, @@ -980,7 +988,10 @@ static PSI_mutex_info all_server_mutexes[]= { &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_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, @@ -1015,7 +1026,7 @@ PSI_cond_key key_BINLOG_COND_xid_list, 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; @@ -1024,9 +1035,12 @@ PSI_cond_key key_COND_wsrep_rollback, key_COND_wsrep_thd, key_COND_wsrep_replaying, key_COND_wsrep_ready, key_COND_wsrep_sst, key_COND_wsrep_sst_init, key_COND_wsrep_sst_thread; #endif /* WITH_WSREP */ -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, key_COND_rpl_thread_pool, + key_COND_parallel_entry, key_COND_prepare_ordered; static PSI_cond_info all_server_conds[]= { @@ -1047,6 +1061,7 @@ static PSI_cond_info all_server_conds[]= { &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_server_started, "COND_server_started", PSI_FLAG_GLOBAL}, @@ -1061,7 +1076,7 @@ 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}, @@ -1075,13 +1090,17 @@ static PSI_cond_info all_server_conds[]= { &key_COND_wsrep_thd, "THD::COND_wsrep_thd", 0}, { &key_COND_wsrep_replaying, "COND_wsrep_replaying", PSI_FLAG_GLOBAL}, #endif - { &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_pool, "COND_rpl_thread_pool", 0}, + { &key_COND_parallel_entry, "COND_parallel_entry", 0}, + { &key_COND_prepare_ordered, "COND_prepare_ordered", 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_slave_init; + key_thread_slave_init, key_rpl_parallel_thread; static PSI_thread_info all_server_threads[]= { @@ -1107,7 +1126,8 @@ static PSI_thread_info all_server_threads[]= { &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_slave_init, "slave_init", PSI_FLAG_GLOBAL} + { &key_thread_slave_init, "slave_init", PSI_FLAG_GLOBAL}, + { &key_rpl_parallel_thread, "rpl_parallel_thread", 0} }; #ifdef HAVE_MMAP @@ -1354,7 +1374,7 @@ struct my_rnd_struct sql_rand; ///< used by sql_class.cc:THD::THD() @param level log message level @param format log message format string */ - +C_MODE_START static void buffered_option_error_reporter(enum loglevel level, const char *format, ...) { @@ -1367,6 +1387,33 @@ static void buffered_option_error_reporter(enum loglevel level, buffered_logs.buffer(level, buffer); } + +/** + 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 @@ -2112,7 +2159,6 @@ void clean_up(bool print_message) delete global_rpl_filter; end_ssl(); vio_end(); - my_regex_end(); #if defined(ENABLED_DEBUG_SYNC) /* End the debug sync facility. See debug_sync.cc. */ debug_sync_end(); @@ -2135,6 +2181,7 @@ void clean_up(bool print_message) my_atomic_rwlock_destroy(&thread_running_lock); my_atomic_rwlock_destroy(&thread_count_lock); my_atomic_rwlock_destroy(&statistics_lock); + my_atomic_rwlock_destroy(&slave_executed_entries_lock); free_charsets(); mysql_mutex_lock(&LOCK_thread_count); DBUG_PRINT("quit", ("got thread count lock")); @@ -2231,6 +2278,7 @@ 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_commit_ordered); DBUG_VOID_RETURN; } @@ -2945,6 +2993,9 @@ bool one_thread_per_connection_end(THD *thd, bool put_in_cache) mysql_mutex_unlock(&LOCK_thread_count); } DBUG_LEAVE; // Must match DBUG_ENTER() +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + ERR_remove_state(0); +#endif my_thread_end(); pthread_exit(0); @@ -3520,9 +3571,9 @@ 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); +extern "C" void *my_str_realloc_mysqld(void *ptr, size_t size); void *my_str_malloc_mysqld(size_t size) { @@ -3534,7 +3585,11 @@ void my_str_free_mysqld(void *ptr) { my_free(ptr); } -#endif /* EMBEDDED_LIBRARY */ + +void *my_str_realloc_mysqld(void *ptr, size_t size) +{ + return my_realloc(ptr, size, MYF(MY_FAE)); +} #ifdef __WIN__ @@ -3566,24 +3621,57 @@ sizeof(load_default_groups)/sizeof(load_default_groups[0]); /** 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; + return check_enough_stack_size_slow(); +} +#endif - THD *my_thd= current_thd; - if (my_thd != NULL) - return check_stack_overrun(my_thd, STACK_MIN_SIZE * 2, &stack_top); - return 0; + + +/* + Initialize my_str_malloc() and my_str_free() +*/ +static void init_libstrings() +{ + my_str_malloc= &my_str_malloc_mysqld; + my_str_free= &my_str_free_mysqld; + my_str_realloc= &my_str_realloc_mysqld; +#ifndef EMBEDDED_LIBRARY + my_string_stack_guard= check_enough_stack_size; +#endif } + + +static void init_pcre() +{ + pcre_malloc= pcre_stack_malloc= my_str_malloc_mysqld; + pcre_free= pcre_stack_free= my_str_free_mysqld; +#ifndef EMBEDDED_LIBRARY + pcre_stack_guard= check_enough_stack_size_slow; #endif +} /** @@ -3618,7 +3706,6 @@ static bool init_global_datetime_format(timestamp_type format_type, SHOW_VAR com_status_vars[]= { {"admin_commands", (char*) offsetof(STATUS_VAR, com_other), SHOW_LONG_STATUS}, - {"assign_to_keycache", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ASSIGN_TO_KEYCACHE]), 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}, @@ -3628,6 +3715,7 @@ SHOW_VAR com_status_vars[]= { {"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}, @@ -3641,6 +3729,7 @@ SHOW_VAR com_status_vars[]= { {"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_role", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_ROLE]), 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}, @@ -3656,6 +3745,7 @@ SHOW_VAR com_status_vars[]= { {"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_role", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_ROLE]), 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}, @@ -3666,6 +3756,7 @@ SHOW_VAR com_status_vars[]= { {"flush", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_FLUSH]), SHOW_LONG_STATUS}, {"get_diagnostics", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_GET_DIAGNOSTICS]), SHOW_LONG_STATUS}, {"grant", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_GRANT]), SHOW_LONG_STATUS}, + {"grant_role", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_GRANT_ROLE]), 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}, @@ -3691,12 +3782,12 @@ SHOW_VAR com_status_vars[]= { {"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}, + {"revoke_role", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_REVOKE_ROLE]), 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}, - {"signal", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SIGNAL]), 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}, @@ -3714,8 +3805,8 @@ SHOW_VAR com_status_vars[]= { {"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_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EVENTS]), 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_explain", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EXPLAIN]), SHOW_LONG_STATUS}, {"show_fields", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FIELDS]), SHOW_LONG_STATUS}, #ifndef DBUG_OFF @@ -3723,8 +3814,8 @@ SHOW_VAR com_status_vars[]= { #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_keys", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_KEYS]), 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}, @@ -3749,6 +3840,7 @@ SHOW_VAR com_status_vars[]= { {"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}, {"shutdown", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHUTDOWN]), SHOW_LONG_STATUS}, + {"signal", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SIGNAL]), SHOW_LONG_STATUS}, {"start_all_slaves", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SLAVE_ALL_START]), SHOW_LONG_STATUS}, {"start_slave", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SLAVE_START]), SHOW_LONG_STATUS}, {"stmt_close", (char*) offsetof(STATUS_VAR, com_stmt_close), SHOW_LONG_STATUS}, @@ -3912,6 +4004,7 @@ static int init_common_variables() set_current_thd(0); set_malloc_size_cb(my_malloc_size_cb_func); + init_libstrings(); tzset(); // Set tzname sf_leaking_memory= 0; // no memory leaks from now on @@ -4224,12 +4317,7 @@ static int init_common_variables() 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 @@ -4429,6 +4517,7 @@ static int init_thread_environment() &LOCK_rpl_gtid_state, MY_MUTEX_INIT_SLOW); 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_commit_ordered, &LOCK_commit_ordered, MY_MUTEX_INIT_SLOW); @@ -4582,6 +4671,7 @@ static void init_ssl() 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"); @@ -4721,6 +4811,15 @@ static int init_server_components() buffered_logs.cleanup(); #endif /* WITH_PERFSCHEMA_STORAGE_ENGINE */ +#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 + if (xid_cache_init()) { sql_print_error("Out of memory"); @@ -5922,12 +6021,6 @@ 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 /* WSREP AFTER SE */ if (wsrep_recovery) { @@ -8262,6 +8355,42 @@ static int show_default_keycache(THD *thd, SHOW_VAR *var, char *buff) return 0; } +#ifndef DBUG_OFF +static int debug_status_func(THD *thd, SHOW_VAR *var, char *buff) +{ +#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", (char*) &role_global_merges, SHOW_ULONG}, + {"db", (char*) &role_db_merges, SHOW_ULONG}, + {"table", (char*) &role_table_merges, SHOW_ULONG}, + {"column", (char*) &role_column_merges, SHOW_ULONG}, + {"routine", (char*) &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) { @@ -8301,6 +8430,9 @@ SHOW_VAR status_vars[]= { {"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}, +#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}, @@ -8379,7 +8511,7 @@ SHOW_VAR status_vars[]= { {"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}, + {"Slave_open_temp_tables", (char*) &slave_open_temp_tables, SHOW_INT}, #ifdef HAVE_REPLICATION {"Slave_heartbeat_period", (char*) &show_heartbeat_period, SHOW_SIMPLE_FUNC}, {"Slave_received_heartbeats",(char*) &show_slave_received_heartbeats, SHOW_SIMPLE_FUNC}, @@ -8669,6 +8801,7 @@ static int mysql_init_variables(void) my_atomic_rwlock_init(&thread_running_lock); my_atomic_rwlock_init(&thread_count_lock); my_atomic_rwlock_init(&statistics_lock); + my_atomic_rwlock_init(slave_executed_entries_lock); strmov(server_version, MYSQL_SERVER_VERSION); threads.empty(); thread_cache.empty(); @@ -9962,6 +10095,7 @@ PSI_stage_info stage_slave_waiting_event_from_coordinator= { 0, "Waiting for an 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}; #ifdef HAVE_PSI_INTERFACE diff --git a/sql/mysqld.h b/sql/mysqld.h index c9cfb8d1094..0fa51ff08d9 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -159,7 +159,7 @@ 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; @@ -182,6 +182,10 @@ 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_parallel_max_queued; +extern ulong opt_binlog_commit_wait_count; +extern ulong opt_binlog_commit_wait_usec; extern ulong back_log; extern ulong executed_events; extern char language[FN_REFLEN]; @@ -261,15 +265,16 @@ extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_xid_list, key_master_info_sleep_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; +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_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_rpl_gtid_state; @@ -292,16 +297,20 @@ extern PSI_cond_key key_BINLOG_COND_xid_list, 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_pool, + key_COND_parallel_entry; 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_slave_init; + key_thread_one_connection, key_thread_signal_hand, key_thread_slave_init, + 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, @@ -432,6 +441,7 @@ extern PSI_stage_info stage_slave_waiting_workers_to_exit; 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; #ifdef HAVE_PSI_STATEMENT_INTERFACE /** Statement instrumentation keys (sql). @@ -473,7 +483,6 @@ extern uint mysql_data_home_len; extern uint mysql_real_data_home_len; extern const char *mysql_real_data_home_ptr; extern ulong thread_handling; -extern MYSQL_PLUGIN_IMPORT char *mysql_data_home; extern "C" MYSQL_PLUGIN_IMPORT char server_version[SERVER_VERSION_LENGTH]; extern MYSQL_PLUGIN_IMPORT char mysql_real_data_home[]; extern char mysql_unpacked_real_data_home[]; @@ -509,6 +518,7 @@ extern mysql_cond_t COND_manager; extern int32 thread_running; extern int32 thread_count; extern my_atomic_rwlock_t thread_running_lock, thread_count_lock; +extern my_atomic_rwlock_t slave_executed_entries_lock; extern char *opt_ssl_ca, *opt_ssl_capath, *opt_ssl_cert, *opt_ssl_cipher, *opt_ssl_key, *opt_ssl_crl, *opt_ssl_crlpath; @@ -659,6 +669,20 @@ inline void thread_safe_decrement32(int32 *value, my_atomic_rwlock_t *lock) my_atomic_rwlock_wrunlock(lock); } +inline void thread_safe_increment64(int64 *value, my_atomic_rwlock_t *lock) +{ + my_atomic_rwlock_wrlock(lock); + (void) my_atomic_add64(value, 1); + my_atomic_rwlock_wrunlock(lock); +} + +inline void thread_safe_decrement64(int64 *value, my_atomic_rwlock_t *lock) +{ + my_atomic_rwlock_wrlock(lock); + (void) my_atomic_add64(value, -1); + my_atomic_rwlock_wrunlock(lock); +} + inline void inc_thread_running() { diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 8d959e8028a..384c99cff54 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -861,6 +861,14 @@ 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; @@ -2955,6 +2963,8 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, 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)); @@ -2986,6 +2996,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, param.using_real_indexes= TRUE; param.remove_jump_scans= TRUE; 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, @@ -3197,6 +3208,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 @@ -3204,6 +3216,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, thd->no_errors=0; } + DBUG_EXECUTE("info", print_quick(quick, &needed_reg);); /* @@ -10493,6 +10506,7 @@ 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); @@ -11811,6 +11825,26 @@ int QUICK_SELECT_DESC::get_next() if (!(last_range= rev_it++)) DBUG_RETURN(HA_ERR_END_OF_FILE); // All ranges used + key_range start_key; + start_key.key= (const uchar*) last_range->min_key; + start_key.length= last_range->min_length; + start_key.flag= ((last_range->flag & NEAR_MIN) ? HA_READ_AFTER_KEY : + (last_range->flag & EQ_RANGE) ? + HA_READ_KEY_EXACT : HA_READ_KEY_OR_NEXT); + start_key.keypart_map= last_range->min_keypart_map; + key_range end_key; + end_key.key= (const uchar*) last_range->max_key; + end_key.length= last_range->max_length; + end_key.flag= (last_range->flag & NEAR_MAX ? HA_READ_BEFORE_KEY : + HA_READ_AFTER_KEY); + end_key.keypart_map= last_range->max_keypart_map; + result= file->prepare_range_scan((last_range->flag & NO_MIN_RANGE) ? NULL : &start_key, + (last_range->flag & NO_MAX_RANGE) ? NULL : &end_key); + if (result) + { + DBUG_RETURN(result); + } + if (last_range->flag & NO_MAX_RANGE) // Read last record { int local_error; @@ -11966,78 +12000,134 @@ 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 *alloc) { - bool first= TRUE; - - add_key_name(str, &first); + Explain_quick_select *res; + if ((res= new (alloc) Explain_quick_select(QS_TYPE_RANGE))) + res->range.set(alloc, head->key_info[index].name, max_used_key_length); + return res; +} + + +Explain_quick_select* QUICK_GROUP_MIN_MAX_SELECT::get_explain(MEM_ROOT *alloc) +{ + Explain_quick_select *res; + if ((res= new (alloc) Explain_quick_select(QS_TYPE_GROUP_MIN_MAX))) + res->range.set(alloc, head->key_info[index].name, max_used_key_length); + return res; } -void QUICK_INDEX_MERGE_SELECT::add_info_string(String *str) + +Explain_quick_select* QUICK_INDEX_SORT_SELECT::get_explain(MEM_ROOT *alloc) { + Explain_quick_select *res; + if (!(res= new (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(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(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 *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 (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(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(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 *alloc) { - bool first= TRUE; + Explain_quick_select *res; + Explain_quick_select *child_explain; + + if (!(res= new (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(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(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 *alloc) { + Explain_quick_select *res; + Explain_quick_select *child_explain; + + if (!(res= new (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(alloc))) + res->children.push_back(child_explain); else - str->append(','); - quick->add_info_string(str); + return NULL; } - str->append(')'); + + return res; } @@ -12437,8 +12527,12 @@ 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)) + /* + Check (B1) - if current index is covering. Exclude UNIQUE indexes, because + loose scan may still be chosen for them due to imperfect cost calculations. + */ + if (!table->covering_keys.is_set(cur_index) || + cur_index_info->flags & HA_NOSAME) goto next_index; /* diff --git a/sql/opt_range.h b/sql/opt_range.h index 3dbdce00e9d..4f2ab6df60d 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -52,7 +52,7 @@ typedef struct st_key_part { Field::imagetype image_type; } KEY_PART; - +class Explain_quick_select; /* A "MIN_TUPLE < tbl.key_tuple < MAX_TUPLE" interval. @@ -345,13 +345,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 @@ -478,7 +473,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 @@ -615,6 +610,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); @@ -663,7 +659,6 @@ public: 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 @@ -679,7 +674,7 @@ public: 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); }; @@ -717,7 +712,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); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -796,7 +791,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); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -944,11 +939,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); }; @@ -990,6 +982,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(); diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 7d6d58a3414..c156c1642e7 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -437,9 +437,7 @@ Currently, solution #2 is implemented. static bool subquery_types_allow_materialization(Item_in_subselect *in_subs); -static bool replace_where_subcondition(JOIN *join, Item **expr, - Item *old_cond, Item *new_cond, - bool do_fix_fields); +static bool replace_where_subcondition(JOIN *, Item **, Item *, Item *, bool); static int subq_sj_candidate_cmp(Item_in_subselect* el1, Item_in_subselect* el2, void *arg); static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred); @@ -1272,11 +1270,11 @@ void get_delayed_table_estimates(TABLE *table, @brief Replaces an expression destructively inside the expression tree of the WHERE clase. - @note Because of current requirements for semijoin flattening, we do not - need to recurse here, hence this function will only examine the top-level - AND conditions. (see JOIN::prepare, comment starting with "Check if the - subquery predicate can be executed via materialization". - + @note We substitute AND/OR structure because it was copied by + copy_andor_structure and some changes could be done in the copy but + should be left permanent, also there could be several layers of AND over + AND and OR over OR because ::fix_field() possibly is not called. + @param join The top-level query. @param old_cond The expression to be replaced. @param new_cond The expression to be substituted. @@ -1304,13 +1302,20 @@ static bool replace_where_subcondition(JOIN *join, Item **expr, Item *item; while ((item= li++)) { - if (item == old_cond) + if (item == old_cond) { li.replace(new_cond); if (do_fix_fields) new_cond->fix_fields(join->thd, li.ref()); return FALSE; } + else if (item->type() == Item::COND_ITEM) + { + DBUG_ASSERT(!do_fix_fields || !(*expr)->fixed); + replace_where_subcondition(join, li.ref(), + old_cond, new_cond, + do_fix_fields); + } } } /* @@ -5223,10 +5228,12 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, { eq_cond= new Item_func_eq(subq_pred->left_expr->element_index(i), new_sink->row[i]); - if (!eq_cond || eq_cond->fix_fields(join->thd, &eq_cond)) + if (!eq_cond) DBUG_RETURN(1); - (*join_where)= and_items(*join_where, eq_cond); + if (!((*join_where)= and_items(*join_where, eq_cond)) || + (*join_where)->fix_fields(join->thd, join_where)) + DBUG_RETURN(1); } } else @@ -5241,6 +5248,12 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, DBUG_RETURN(1); table->table= dummy_table; table->table->pos_in_table_list= table; + /* + Note: the table created above may be freed by: + 1. JOIN_TAB::cleanup(), when the parent join is a regular join. + 2. cleanup_empty_jtbm_semi_joins(), when the parent join is a + degenerate join (e.g. one with "Impossible where"). + */ setup_table_map(table->table, table, table->jtbm_table_no); } else @@ -5273,6 +5286,42 @@ bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, } +/* + Cleanup non-merged semi-joins (JBMs) that have empty. + + This function is to cleanups for a special case: + Consider a query like + + select * from t1 where 1=2 AND t1.col IN (select max(..) ... having 1=2) + + For this query, optimization of subquery will short-circuit, and + setup_jtbm_semi_joins() will call create_dummy_tmp_table() so that we have + empty, constant temp.table to stand in as materialized temp. table. + + Now, suppose that the upper join is also found to be degenerate. In that + case, no JOIN_TAB array will be produced, and hence, JOIN::cleanup() will + have a problem with cleaning up empty JTBMs (non-empty ones are cleaned up + through Item::cleanup() calls). +*/ + +void cleanup_empty_jtbm_semi_joins(JOIN *join) +{ + List_iterator<TABLE_LIST> li(*join->join_list); + TABLE_LIST *table; + while ((table= li++)) + { + if ((table->jtbm_subselect && table->jtbm_subselect->is_jtbm_const_tab)) + { + if (table->table) + { + free_tmp_table(join->thd, table->table); + table->table= NULL; + } + } + } +} + + /** Choose an optimal strategy to execute an IN/ALL/ANY subquery predicate based on cost. diff --git a/sql/opt_subselect.h b/sql/opt_subselect.h index 01da437504b..6ed077dcfcb 100644 --- a/sql/opt_subselect.h +++ b/sql/opt_subselect.h @@ -28,6 +28,7 @@ int pull_out_semijoin_tables(JOIN *join); bool optimize_semijoin_nests(JOIN *join, table_map all_table_map); bool setup_jtbm_semi_joins(JOIN *join, List<TABLE_LIST> *join_list, Item **join_where); +void cleanup_empty_jtbm_semi_joins(JOIN *join); // used by Loose_scan_opt ulonglong get_bound_sj_equalities(TABLE_LIST *sj_nest, @@ -191,7 +192,7 @@ public: (PREV_BITS(key_part_map, max_loose_keypart+1) & // (3) (found_part | loose_scan_keyparts)) == // (3) PREV_BITS(key_part_map, max_loose_keypart+1) && // (3) - !key_uses_partial_cols(s->table, key)) + !key_uses_partial_cols(s->table->s, key)) { /* Ok, can use the strategy */ part1_conds_met= TRUE; diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc index b8d39057ba8..d4fc458c948 100644 --- a/sql/opt_sum.cc +++ b/sql/opt_sum.cc @@ -479,6 +479,24 @@ int opt_sum_query(THD *thd, } +/* + Check if both item1 and item2 are strings, and item1 has fewer characters + than item2. +*/ + +static bool check_item1_shorter_item2(Item *item1, Item *item2) +{ + if (item1->cmp_type() == STRING_RESULT && + item2->cmp_type() == STRING_RESULT) + { + int len1= item1->max_length / item1->collation.collation->mbmaxlen; + int len2= item2->max_length / item2->collation.collation->mbmaxlen; + return len1 < len2; + } + return false; /* When the check is not applicable, it means "not bigger" */ +} + + /** Test if the predicate compares a field with constants. @@ -509,7 +527,7 @@ bool simple_pred(Item_func *func_item, Item **args, bool *inv_order) if (!(item= it++)) return 0; args[0]= item->real_item(); - if (args[0]->max_length < args[1]->max_length) + if (check_item1_shorter_item2(args[0], args[1])) return 0; if (it++) return 0; @@ -544,7 +562,7 @@ bool simple_pred(Item_func *func_item, Item **args, bool *inv_order) } else return 0; - if (args[0]->max_length < args[1]->max_length) + if (check_item1_shorter_item2(args[0], args[1])) return 0; break; case 3: @@ -559,7 +577,7 @@ bool simple_pred(Item_func *func_item, Item **args, bool *inv_order) if (!item->const_item()) return 0; args[i]= item; - if (args[0]->max_length < args[i]->max_length) + if (check_item1_shorter_item2(args[0], args[1])) return 0; } } diff --git a/sql/opt_table_elimination.cc b/sql/opt_table_elimination.cc index 7454e756416..e50a9b0b42d 100644 --- a/sql/opt_table_elimination.cc +++ b/sql/opt_table_elimination.cc @@ -892,8 +892,11 @@ bool Dep_analysis_context::run_wave(List<Dep_module> *new_bound_modules) iter= module->init_unbound_values_iter(iter_buf); while ((value= module->get_next_unbound_value(this, iter))) { - value->make_bound(); - new_bound_values.push_back(value); + if (!value->is_bound()) + { + value->make_bound(); + new_bound_values.push_back(value); + } } } new_bound_modules->empty(); @@ -1740,7 +1743,7 @@ Dep_module* Dep_value_field::get_next_unbound_module(Dep_analysis_context *dac, - have this field as a part of them */ while (key_dep && (key_dep->is_applicable() || - !field->part_of_key.is_set(key_dep->keyno))) + !field->part_of_key_not_clustered.is_set(key_dep->keyno))) { key_dep= key_dep->next_table_key; } diff --git a/sql/records.cc b/sql/records.cc index d28799ea9d2..e534c04935a 100644 --- a/sql/records.cc +++ b/sql/records.cc @@ -371,7 +371,15 @@ static int rr_quick(READ_RECORD *info) static int rr_index_first(READ_RECORD *info) { - int tmp= info->table->file->ha_index_first(info->record); + int tmp; + // tell handler that we are doing an index scan + if ((tmp = info->table->file->prepare_index_scan())) + { + tmp= rr_handle_error(info, tmp); + return tmp; + } + + tmp= info->table->file->ha_index_first(info->record); info->read_record= rr_index; if (tmp) tmp= rr_handle_error(info, tmp); diff --git a/sql/rpl_gtid.cc b/sql/rpl_gtid.cc index 05ac79a8f64..8429f1c7f58 100644 --- a/sql/rpl_gtid.cc +++ b/sql/rpl_gtid.cc @@ -62,27 +62,28 @@ rpl_slave_state::update_state_hash(uint64 sub_id, rpl_gtid *gtid) int -rpl_slave_state::record_and_update_gtid(THD *thd, Relay_log_info *rli) +rpl_slave_state::record_and_update_gtid(THD *thd, rpl_group_info *rgi) { uint64 sub_id; + 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 ((sub_id= rli->gtid_sub_id)) + if ((sub_id= rgi->gtid_sub_id)) { - rli->gtid_sub_id= 0; - if (record_gtid(thd, &rli->current_gtid, sub_id, false, false)) - return 1; - update_state_hash(sub_id, &rli->current_gtid); + rgi->gtid_sub_id= 0; + if (record_gtid(thd, &rgi->current_gtid, sub_id, false, false)) + DBUG_RETURN(1); + update_state_hash(sub_id, &rgi->current_gtid); } - return 0; + DBUG_RETURN(0); } rpl_slave_state::rpl_slave_state() - : inited(false), loaded(false) + : last_sub_id(0), inited(false), loaded(false) { my_hash_init(&hash, &my_charset_bin, 32, offsetof(element, domain_id), sizeof(uint32), NULL, my_free, HASH_UNIQUE); @@ -152,6 +153,9 @@ rpl_slave_state::update(uint32 domain_id, uint32 server_id, uint64 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; } @@ -168,7 +172,6 @@ rpl_slave_state::get_element(uint32 domain_id) if (!(elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME)))) return NULL; elem->list= NULL; - elem->last_sub_id= 0; elem->domain_id= domain_id; if (my_hash_insert(&hash, (uchar *)elem)) { @@ -311,6 +314,7 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, element *elem; ulonglong thd_saved_option= thd->variables.option_bits; Query_tables_list lex_backup; + DBUG_ENTER("record_gtid"); if (unlikely(!loaded)) { @@ -321,16 +325,16 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, We already complained loudly about this, but we can try to continue until the DBA fixes it. */ - return 0; + DBUG_RETURN(0); } if (!in_statement) - mysql_reset_thd_for_next_command(thd, 0); + mysql_reset_thd_for_next_command(thd); DBUG_EXECUTE_IF("gtid_inject_record_gtid", { my_error(ER_CANNOT_UPDATE_GTID_STATE, MYF(0)); - return 1; + DBUG_RETURN(1); } ); thd->lex->reset_n_backup_query_tables_list(&lex_backup); @@ -349,8 +353,11 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, table->no_replicate= 1; table->s->is_gtid_slave_pos= TRUE; // TEMPORARY CODE if (!in_transaction) + { + DBUG_PRINT("info", ("resetting OPTION_BEGIN")); thd->variables.option_bits&= ~(ulonglong)(OPTION_NOT_AUTOCOMMIT|OPTION_BEGIN); + } bitmap_set_all(table->write_set); @@ -485,7 +492,7 @@ end: } thd->lex->restore_backup_query_tables_list(&lex_backup); thd->variables.option_bits= thd_saved_option; - return err; + DBUG_RETURN(err); } @@ -493,12 +500,9 @@ uint64 rpl_slave_state::next_sub_id(uint32 domain_id) { uint64 sub_id= 0; - element *elem; lock(); - elem= get_element(domain_id); - if (elem) - sub_id= ++elem->last_sub_id; + sub_id= ++last_sub_id; unlock(); return sub_id; @@ -721,6 +725,45 @@ gtid_parser_helper(char **ptr, char *end, rpl_gtid *out_gtid) } +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. @@ -1236,11 +1279,46 @@ rpl_binlog_state::append_pos(String *str) } +bool +rpl_binlog_state::append_state(String *str) +{ + uint32 i, j; + bool first= true; + + 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 (rpl_slave_state_tostring_helper(str, gtid, &first)) + return true; + } + } + + return false; +} + + slave_connection_state::slave_connection_state() { my_hash_init(&hash, &my_charset_bin, 32, - offsetof(rpl_gtid, domain_id), sizeof(uint32), NULL, my_free, - HASH_UNIQUE); + offsetof(entry, gtid) + offsetof(rpl_gtid, domain_id), + sizeof(uint32), NULL, my_free, HASH_UNIQUE); } @@ -1274,7 +1352,7 @@ slave_connection_state::load(char *slave_request, size_t len) char *p, *end; uchar *rec; rpl_gtid *gtid; - const rpl_gtid *gtid2; + const entry *e; reset(); p= slave_request; @@ -1283,27 +1361,28 @@ slave_connection_state::load(char *slave_request, size_t len) return 0; for (;;) { - if (!(rec= (uchar *)my_malloc(sizeof(*gtid), MYF(MY_WME)))) + if (!(rec= (uchar *)my_malloc(sizeof(entry), MYF(MY_WME)))) { my_error(ER_OUTOFMEMORY, MYF(0), sizeof(*gtid)); return 1; } - gtid= (rpl_gtid *)rec; + 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 ((gtid2= (const rpl_gtid *) + 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, gtid2->domain_id, - gtid2->server_id, (ulonglong)gtid2->seq_no, 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); @@ -1359,30 +1438,42 @@ slave_connection_state::load(rpl_slave_state *state, } +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) { - return (rpl_gtid *) my_hash_search(&hash, (const uchar *)(&domain_id), 0); + entry *e= find_entry(domain_id); + if (!e) + return NULL; + return &e->gtid; } int slave_connection_state::update(const rpl_gtid *in_gtid) { - rpl_gtid *new_gtid; + entry *e; uchar *rec= my_hash_search(&hash, (const uchar *)(&in_gtid->domain_id), 0); if (rec) { - memcpy(rec, in_gtid, sizeof(*in_gtid)); + e= (entry *)rec; + e->gtid= *in_gtid; return 0; } - if (!(new_gtid= (rpl_gtid *)my_malloc(sizeof(*new_gtid), MYF(MY_WME)))) + if (!(e= (entry *)my_malloc(sizeof(*e), MYF(MY_WME)))) return 1; - memcpy(new_gtid, in_gtid, sizeof(*new_gtid)); - if (my_hash_insert(&hash, (uchar *)new_gtid)) + e->gtid= *in_gtid; + e->flags= 0; + if (my_hash_insert(&hash, (uchar *)e)) { - my_free(new_gtid); + my_free(e); return 1; } @@ -1396,7 +1487,7 @@ 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= (rpl_gtid *)rec; + 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); @@ -1408,6 +1499,15 @@ slave_connection_state::remove(const rpl_gtid *in_gtid) } +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) { @@ -1425,9 +1525,28 @@ slave_connection_state::append_to_string(String *out_str) first= true; for (i= 0; i < hash.records; ++i) { - const rpl_gtid *gtid= (const rpl_gtid *)my_hash_element(&hash, i); - if (rpl_slave_state_tostring_helper(out_str, gtid, &first)) + 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; } diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h index 1a94ee76eca..a503184cee6 100644 --- a/sql/rpl_gtid.h +++ b/sql/rpl_gtid.h @@ -60,7 +60,6 @@ struct rpl_slave_state struct element { struct list_element *list; - uint64 last_sub_id; uint32 domain_id; list_element *grab_list() { list_element *l= list; list= NULL; return l; } @@ -68,8 +67,6 @@ struct rpl_slave_state { l->next= list; list= l; - if (last_sub_id < l->sub_id) - last_sub_id= l->sub_id; } }; @@ -78,6 +75,7 @@ struct rpl_slave_state /* Mutex protecting access to the state. */ mysql_mutex_t LOCK_slave_state; + uint64 last_sub_id; bool inited; bool loaded; @@ -108,7 +106,7 @@ struct rpl_slave_state int put_back_list(uint32 domain_id, list_element *list); void update_state_hash(uint64 sub_id, rpl_gtid *gtid); - int record_and_update_gtid(THD *thd, Relay_log_info *rli); + int record_and_update_gtid(THD *thd, struct rpl_group_info *rgi); }; @@ -163,6 +161,7 @@ struct rpl_binlog_state 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(uint32 domain_id, uint32 server_id); rpl_gtid *find_most_recent(uint32 domain_id); }; @@ -174,7 +173,14 @@ struct rpl_binlog_state */ struct slave_connection_state { - /* Mapping from domain_id to the GTID requested for that domain. */ + 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; slave_connection_state(); @@ -185,15 +191,20 @@ struct slave_connection_state 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); }; 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_mi.h b/sql/rpl_mi.h index 991f6673c3a..a136af38356 100644 --- a/sql/rpl_mi.h +++ b/sql/rpl_mi.h @@ -162,6 +162,8 @@ class Master_info : public Slave_reporting_capability 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, diff --git a/sql/rpl_parallel.cc b/sql/rpl_parallel.cc new file mode 100644 index 00000000000..80125e8aa29 --- /dev/null +++ b/sql/rpl_parallel.cc @@ -0,0 +1,1022 @@ +#include "my_global.h" +#include "rpl_parallel.h" +#include "slave.h" +#include "rpl_mi.h" + + +/* + Code for optional parallel execution of replicated events on the slave. + + ToDo list: + + - Retry of failed transactions is not yet implemented for the parallel case. + + - All the waits (eg. in struct wait_for_commit and in + rpl_parallel_thread_pool::get_thread()) need to be killable. And on kill, + everything needs to be correctly rolled back and stopped in all threads, + to ensure a consistent slave replication state. +*/ + +struct rpl_parallel_thread_pool global_rpl_thread_pool; + + +static int +rpt_handle_event(rpl_parallel_thread::queued_event *qev, + struct rpl_parallel_thread *rpt) +{ + int err __attribute__((unused)); + rpl_group_info *rgi= qev->rgi; + Relay_log_info *rli= rgi->rli; + THD *thd= rgi->thd; + + thd->rgi_slave= rgi; + thd->rpl_filter = rli->mi->rpl_filter; + + /* ToDo: Access to thd, and what about rli, split out a parallel part? */ + mysql_mutex_lock(&rli->data_lock); + qev->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); + err= apply_event_and_update_pos(qev->ev, thd, rgi, rpt); + thd->rgi_slave= NULL; + + thread_safe_increment64(&rli->executed_entries, + &slave_executed_entries_lock); + /* 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; + /* + 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; + 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->notify_group_master_log_name_update(); + 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); +} + + +static bool +sql_worker_killed(THD *thd, rpl_group_info *rgi, bool in_event_group) +{ + if (!rgi->rli->abort_slave && !abort_loop) + return false; + + /* + Do not abort in the middle of an event group that cannot be rolled back. + */ + if ((thd->transaction.all.modified_non_trans_table || + (thd->variables.option_bits & OPTION_KEEP_LOG)) + && in_event_group) + return false; + /* ToDo: should we add some timeout like in sql_slave_killed? + if (rgi->last_event_start_time == 0) + rgi->last_event_start_time= my_time(0); + */ + + return true; +} + + +static void +finish_event_group(THD *thd, int err, uint64 sub_id, + rpl_parallel_entry *entry, wait_for_commit *wfc) +{ + /* + 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. + + But in the error case, we have to abort anyway, and it seems best + to just complete as quickly as possible with unregister. Anyone + waiting for us will in any case receive the error back from their + wait_for_prior_commit() call. + */ + if (err) + wfc->unregister_wait_for_prior_commit(); + else + wfc->wait_for_prior_commit(); + thd->wait_for_commit_ptr= NULL; + + /* + 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. + */ + mysql_mutex_lock(&entry->LOCK_parallel_entry); + if (entry->last_committed_sub_id < sub_id) + { + entry->last_committed_sub_id= sub_id; + mysql_cond_broadcast(&entry->COND_parallel_entry); + } + mysql_mutex_unlock(&entry->LOCK_parallel_entry); + + wfc->wakeup_subsequent_commits(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; + rpl_group_info *group_rgi= NULL; + uint64 event_gtid_sub_id= 0; + 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->enable_slow_log= 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_proc_info(thd, "Waiting for work from main SQL threads"); + thd->set_time(); + thd->variables.lock_wait_timeout= LONG_TIMEOUT; + + 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 && !thd->killed) + { + rpl_parallel_thread *list; + + thd->ENTER_COND(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread, + &stage_waiting_for_work_from_sql_thread, &old_stage); + while (!(events= rpt->event_queue) && !rpt->stop && !thd->killed && + !(rpt->current_entry && rpt->current_entry->force_abort)) + mysql_cond_wait(&rpt->COND_rpl_thread, &rpt->LOCK_rpl_thread); + rpt->dequeue(events); + thd->EXIT_COND(&old_stage); + mysql_cond_signal(&rpt->COND_rpl_thread); + + more_events: + while (events) + { + struct rpl_parallel_thread::queued_event *next= events->next; + Log_event_type event_type; + rpl_group_info *rgi= events->rgi; + rpl_parallel_entry *entry= rgi->parallel_entry; + uint64 wait_for_sub_id; + uint64 wait_start_sub_id; + bool end_of_group; + + if (!events->ev) + { + handle_queued_pos_update(thd, events); + my_free(events); + events= next; + continue; + } + + err= 0; + group_rgi= rgi; + /* Handle a new event group, which will be initiated by a GTID event. */ + if ((event_type= events->ev->get_type_code()) == GTID_EVENT) + { + 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 *>(events->ev)->flags2 & + Gtid_log_event::FL_STANDALONE)); + + /* Save this, as it gets cleared when the event group commits. */ + event_gtid_sub_id= rgi->gtid_sub_id; + + rgi->thd= thd; + + /* + Register ourself to wait for the previous commit, if we need to do + such registration _and_ that previous commit has not already + occured. + + Also do not start parallel execution of this event group until all + prior groups have committed that are not safe to run in parallel with. + */ + wait_for_sub_id= rgi->wait_commit_sub_id; + wait_start_sub_id= rgi->wait_start_sub_id; + if (wait_for_sub_id || wait_start_sub_id) + { + mysql_mutex_lock(&entry->LOCK_parallel_entry); + if (wait_start_sub_id) + { + while (wait_start_sub_id > entry->last_committed_sub_id) + mysql_cond_wait(&entry->COND_parallel_entry, + &entry->LOCK_parallel_entry); + } + rgi->wait_start_sub_id= 0; /* No need to check again. */ + if (wait_for_sub_id > entry->last_committed_sub_id) + { + wait_for_commit *waitee= + &rgi->wait_commit_group_info->commit_orderer; + rgi->commit_orderer.register_wait_for_prior_commit(waitee); + } + mysql_mutex_unlock(&entry->LOCK_parallel_entry); + } + + if(thd->wait_for_commit_ptr) + { + /* + 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. + */ + rgi->cleanup_context(thd, true); + thd->wait_for_commit_ptr->unregister_wait_for_prior_commit(); + thd->wait_for_commit_ptr->wakeup_subsequent_commits(err); + } + thd->wait_for_commit_ptr= &rgi->commit_orderer; + } + + /* + 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 (!rgi->is_error && !sql_worker_killed(thd, rgi, in_event_group)) + err= rpt_handle_event(events, rpt); + else + err= thd->wait_for_prior_commit(); + + end_of_group= + in_event_group && + ((group_standalone && !Log_event::is_part_of_group(event_type)) || + event_type == XID_EVENT || + (event_type == QUERY_EVENT && + (((Query_log_event *)events->ev)->is_commit() || + ((Query_log_event *)events->ev)->is_rollback()))); + + delete_or_keep_event_post_apply(rgi, event_type, events->ev); + my_free(events); + + if (err) + { + rgi->is_error= true; + slave_output_error_info(rgi->rli, thd); + rgi->cleanup_context(thd, true); + rgi->rli->abort_slave= true; + } + if (end_of_group) + { + in_event_group= false; + finish_event_group(thd, err, event_gtid_sub_id, entry, + &rgi->commit_orderer); + delete rgi; + group_rgi= rgi= NULL; + } + + events= next; + } + + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + 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->dequeue(events); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + mysql_cond_signal(&rpt->COND_rpl_thread); + goto more_events; + } + + 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); + group_rgi->is_error= true; + finish_event_group(thd, 1, group_rgi->gtid_sub_id, + group_rgi->parallel_entry, &group_rgi->commit_orderer); + group_rgi->cleanup_context(thd, true); + group_rgi->rli->abort_slave= true; + in_event_group= false; + delete group_rgi; + group_rgi= NULL; + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + } + if (!in_event_group) + { + rpt->current_entry= NULL; + if (!rpt->stop) + { + mysql_mutex_lock(&rpt->pool->LOCK_rpl_thread_pool); + list= rpt->pool->free_list; + rpt->next= list; + rpt->pool->free_list= rpt; + if (!list) + mysql_cond_broadcast(&rpt->pool->COND_rpl_thread_pool); + mysql_mutex_unlock(&rpt->pool->LOCK_rpl_thread_pool); + } + } + } + + 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_CHECK_SENTRY(thd); + delete thd; + mysql_mutex_unlock(&LOCK_thread_count); + + 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; +} + + +int +rpl_parallel_change_thread_count(rpl_parallel_thread_pool *pool, + uint32 new_count, bool skip_check) +{ + uint32 i; + rpl_parallel_thread **new_list= NULL; + rpl_parallel_thread *new_free_list= NULL; + rpl_parallel_thread *rpt_array= NULL; + + /* + 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); + new_list[i]->pool= pool; + if (mysql_thread_create(key_rpl_parallel_thread, &th, NULL, + 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]; + } + + if (!skip_check) + { + mysql_mutex_lock(&LOCK_active_mi); + if (master_info_index->give_error_if_slave_running()) + { + mysql_mutex_unlock(&LOCK_active_mi); + goto err; + } + if (pool->changing) + { + mysql_mutex_unlock(&LOCK_active_mi); + my_error(ER_CHANGE_SLAVE_PARALLEL_THREADS_ACTIVE, MYF(0)); + goto err; + } + pool->changing= true; + mysql_mutex_unlock(&LOCK_active_mi); + } + + /* + 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= pool->get_thread(NULL); + 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); + } + + my_free(pool->threads); + pool->threads= new_list; + pool->free_list= new_free_list; + pool->count= new_count; + 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); + } + + if (!skip_check) + { + mysql_mutex_lock(&LOCK_active_mi); + pool->changing= false; + mysql_mutex_unlock(&LOCK_active_mi); + } + 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); + } + if (!skip_check) + { + mysql_mutex_lock(&LOCK_active_mi); + pool->changing= false; + mysql_mutex_unlock(&LOCK_active_mi); + } + return 1; +} + + +rpl_parallel_thread_pool::rpl_parallel_thread_pool() + : count(0), threads(0), free_list(0), changing(false), inited(false) +{ +} + + +int +rpl_parallel_thread_pool::init(uint32 size) +{ + count= 0; + threads= NULL; + free_list= NULL; + + 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); + changing= false; + inited= true; + + return rpl_parallel_change_thread_count(this, size, true); +} + + +void +rpl_parallel_thread_pool::destroy() +{ + if (!inited) + return; + rpl_parallel_change_thread_count(this, 0, true); + 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_entry *entry) +{ + rpl_parallel_thread *rpt; + + mysql_mutex_lock(&LOCK_rpl_thread_pool); + while ((rpt= free_list) == NULL) + 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_entry= entry; + + return rpt; +} + + +static void +free_rpl_parallel_entry(void *element) +{ + rpl_parallel_entry *e= (rpl_parallel_entry *)element; + 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. */ + if (!(e= (struct rpl_parallel_entry *)my_malloc(sizeof(*e), + MYF(MY_ZEROFILL)))) + return NULL; + e->domain_id= domain_id; + 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; +} + + +void +rpl_parallel::wait_for_done() +{ + struct rpl_parallel_entry *e; + uint32 i; + + /* + 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) + { + rpl_parallel_thread *rpt; + + e= (struct rpl_parallel_entry *)my_hash_element(&domain_hash, i); + e->force_abort= true; + if ((rpt= e->rpl_thread)) + { + mysql_mutex_lock(&rpt->LOCK_rpl_thread); + if (rpt->current_entry == e) + mysql_cond_signal(&rpt->COND_rpl_thread); + mysql_mutex_unlock(&rpt->LOCK_rpl_thread); + } + } + + 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); + while (e->current_sub_id > e->last_committed_sub_id) + mysql_cond_wait(&e->COND_parallel_entry, &e->LOCK_parallel_entry); + mysql_mutex_unlock(&e->LOCK_parallel_entry); + } +} + + +/* + 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 false ok, event was accepted + @retval true error +*/ + +bool +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; + + /* ToDo: what to do with this lock?!? */ + mysql_mutex_unlock(&rli->data_lock); + + /* + Stop queueing additional event groups once the SQL thread is requested to + stop. + */ + if (((typ= ev->get_type_code()) == GTID_EVENT || + !(is_group_event= Log_event::is_group_event(typ))) && + rli->abort_slave) + sql_thread_stopping= true; + if (sql_thread_stopping) + { + /* QQ: Need a better comment why we return false here */ + return false; + } + + if (!(qev= (rpl_parallel_thread::queued_event *)my_malloc(sizeof(*qev), + MYF(0)))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return true; + } + qev->ev= ev; + qev->event_size= event_size; + qev->next= 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); + + if (typ == GTID_EVENT) + { + Gtid_log_event *gtid_ev= static_cast<Gtid_log_event *>(ev); + uint32 domain_id= (rli->mi->using_gtid == Master_info::USE_GTID_NO ? + 0 : gtid_ev->domain_id); + + if (!(e= find(domain_id)) || + !(rgi= new rpl_group_info(rli)) || + event_group_new_gtid(rgi, gtid_ev)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(MY_WME)); + delete rgi; + return true; + } + rgi->is_parallel_exec = true; + if ((rgi->deferred_events_collecting= rli->mi->rpl_filter->is_on())) + rgi->deferred_events= new Deferred_log_events(rli); + + if ((gtid_ev->flags2 & Gtid_log_event::FL_GROUP_COMMIT_ID) && + e->last_commit_id == gtid_ev->commit_id) + { + /* + We are already executing something else in this domain. But the two + event groups were committed together in the same group commit on the + master, so we can still do them in parallel here on the slave. + + However, the commit of this event must wait for the commit of the prior + event, to preserve binlog commit order and visibility across all + servers in the replication hierarchy. + */ + rpl_parallel_thread *rpt= global_rpl_thread_pool.get_thread(e); + rgi->wait_commit_sub_id= e->current_sub_id; + rgi->wait_commit_group_info= e->current_group_info; + rgi->wait_start_sub_id= e->prev_groupcommit_sub_id; + e->rpl_thread= cur_thread= rpt; + /* get_thread() returns with the LOCK_rpl_thread locked. */ + } + else + { + /* + Check if we already have a worker thread for this entry. + + We continue to queue more events up for the worker thread while it is + still executing the first ones, to be able to start executing a large + event group without having to wait for the end to be fetched from the + master. And we continue to queue up more events after the first group, + so that we can continue to process subsequent parts of the relay log in + parallel without having to wait for previous long-running events to + complete. + + But if the worker thread is idle at any point, it may return to the + idle list or start servicing a different request. So check this, and + allocate a new thread if the old one is no longer processing for us. + */ + cur_thread= e->rpl_thread; + if (cur_thread) + { + mysql_mutex_lock(&cur_thread->LOCK_rpl_thread); + for (;;) + { + if (cur_thread->current_entry != e) + { + /* + The worker thread became idle, and returned to the free list and + possibly was allocated to a different request. This also means + that everything previously queued has already been executed, + else the worker thread would not have become idle. So we should + allocate a new worker thread. + */ + mysql_mutex_unlock(&cur_thread->LOCK_rpl_thread); + e->rpl_thread= cur_thread= NULL; + break; + } + else if (cur_thread->queued_size <= opt_slave_parallel_max_queued) + break; // The thread is ready to queue into + 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. + */ + mysql_cond_wait(&cur_thread->COND_rpl_thread, + &cur_thread->LOCK_rpl_thread); + } + } + } + + if (!cur_thread) + { + /* + Nothing else is currently running in this domain. We can + spawn a new thread to do this event group in parallel with + anything else that might be running in other domains. + */ + cur_thread= e->rpl_thread= global_rpl_thread_pool.get_thread(e); + /* get_thread() returns with the LOCK_rpl_thread locked. */ + } + else + { + /* + We are still executing the previous event group for this replication + domain, and we have to wait for that to finish before we can start on + the next one. So just re-use the thread. + */ + } + + rgi->wait_commit_sub_id= 0; + rgi->wait_start_sub_id= 0; + e->prev_groupcommit_sub_id= e->current_sub_id; + } + + if (gtid_ev->flags2 & Gtid_log_event::FL_GROUP_COMMIT_ID) + { + e->last_server_id= gtid_ev->server_id; + e->last_seq_no= gtid_ev->seq_no; + e->last_commit_id= gtid_ev->commit_id; + } + else + { + e->last_server_id= 0; + e->last_seq_no= 0; + e->last_commit_id= 0; + } + + qev->rgi= e->current_group_info= rgi; + e->current_sub_id= rgi->gtid_sub_id; + current= rgi->parallel_entry= e; + } + else if (!is_group_event || !current) + { + my_off_t log_pos; + 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). + + The varuable `current' is NULL for the case where the master did not + have GTID, like a MariaDB 5.5 or MySQL master. + */ + qev->rgi= serial_rgi; + /* Handle master log name change, seen in Rotate_log_event. */ + if (typ == ROTATE_EVENT) + { + Rotate_log_event *rev= static_cast<Rotate_log_event *>(qev->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); + } + } + + tmp= serial_rgi->is_parallel_exec; + serial_rgi->is_parallel_exec= true; + err= rpt_handle_event(qev, NULL); + serial_rgi->is_parallel_exec= tmp; + log_pos= qev->ev->log_pos; + delete_or_keep_event_post_apply(serial_rgi, typ, qev->ev); + + if (err) + { + my_free(qev); + return true; + } + qev->ev= NULL; + qev->future_event_master_log_pos= log_pos; + if (!current) + { + rli->event_relay_log_pos= rli->future_event_relay_log_pos; + handle_queued_pos_update(rli->sql_driver_thd, qev); + my_free(qev); + return false; + } + /* + Queue an empty event, 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. + */ + cur_thread= current->rpl_thread; + if (cur_thread) + { + mysql_mutex_lock(&cur_thread->LOCK_rpl_thread); + if (cur_thread->current_entry != current) + { + /* Not ours anymore, we need to grab a new one. */ + mysql_mutex_unlock(&cur_thread->LOCK_rpl_thread); + cur_thread= NULL; + } + } + if (!cur_thread) + cur_thread= current->rpl_thread= + global_rpl_thread_pool.get_thread(current); + } + else + { + cur_thread= current->rpl_thread; + if (cur_thread) + { + mysql_mutex_lock(&cur_thread->LOCK_rpl_thread); + if (cur_thread->current_entry != current) + { + /* Not ours anymore, we need to grab a new one. */ + mysql_mutex_unlock(&cur_thread->LOCK_rpl_thread); + cur_thread= NULL; + } + } + if (!cur_thread) + { + cur_thread= current->rpl_thread= + global_rpl_thread_pool.get_thread(current); + } + qev->rgi= current->current_group_info; + } + + /* + Queue the event for processing. + */ + rli->event_relay_log_pos= rli->future_event_relay_log_pos; + cur_thread->enqueue(qev); + mysql_mutex_unlock(&cur_thread->LOCK_rpl_thread); + mysql_cond_signal(&cur_thread->COND_rpl_thread); + + return false; +} diff --git a/sql/rpl_parallel.h b/sql/rpl_parallel.h new file mode 100644 index 00000000000..0e88e09652b --- /dev/null +++ b/sql/rpl_parallel.h @@ -0,0 +1,132 @@ +#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 rpl_parallel_thread { + bool delay_start; + bool running; + bool stop; + mysql_mutex_t LOCK_rpl_thread; + mysql_cond_t COND_rpl_thread; + struct rpl_parallel_thread *next; /* For free list. */ + struct rpl_parallel_thread_pool *pool; + THD *thd; + struct rpl_parallel_entry *current_entry; + struct queued_event { + queued_event *next; + Log_event *ev; + rpl_group_info *rgi; + 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; + + 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 dequeue(queued_event *list) + { + queued_event *tmp; + + DBUG_ASSERT(list == event_queue); + event_queue= last_in_queue= NULL; + for (tmp= list; tmp; tmp= tmp->next) + queued_size-= tmp->event_size; + } +}; + + +struct rpl_parallel_thread_pool { + uint32 count; + 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; + bool changing; + bool inited; + + rpl_parallel_thread_pool(); + int init(uint32 size); + void destroy(); + struct rpl_parallel_thread *get_thread(rpl_parallel_entry *entry); +}; + + +struct rpl_parallel_entry { + uint32 domain_id; + uint32 last_server_id; + uint64 last_seq_no; + 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; + + rpl_parallel_thread *rpl_thread; + /* + The sub_id of the last transaction to commit within this domain_id. + Must be accessed under LOCK_parallel_entry protection. + */ + uint64 last_committed_sub_id; + mysql_mutex_t LOCK_parallel_entry; + mysql_cond_t COND_parallel_entry; + /* + 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; + rpl_group_info *current_group_info; + /* + The sub_id of the last event group in the previous batch of group-committed + transactions. + + When we spawn parallel worker threads for the next group-committed batch, + they first need to wait for this sub_id to be committed before it is safe + to start executing them. + */ + uint64 prev_groupcommit_sub_id; +}; +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(); + bool 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_change_thread_count(rpl_parallel_thread_pool *pool, + uint32 new_count, + bool skip_check= false); + +#endif /* RPL_PARALLEL_H */ diff --git a/sql/rpl_record.cc b/sql/rpl_record.cc index c5d60588284..4a2e88150bf 100644 --- a/sql/rpl_record.cc +++ b/sql/rpl_record.cc @@ -186,7 +186,7 @@ pack_row(TABLE *table, MY_BITMAP const* cols, */ #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, uchar const *const row_buffer_end, MY_BITMAP const *cols, @@ -214,18 +214,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) @@ -328,7 +328,7 @@ unpack_row(Relay_log_info const *rli, (table_found) ? "found" : "not found", (ulong)row_buffer_end ); - rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT, + rgi->rli->report(ERROR_LEVEL, ER_SLAVE_CORRUPT_EVENT, "Could not read field '%s' of table '%s.%s'", f->field_name, table->s->db.str, table->s->table_name.str); diff --git a/sql/rpl_record.h b/sql/rpl_record.h index 4b34dcd0a96..7d17d4f7200 100644 --- a/sql/rpl_record.h +++ b/sql/rpl_record.h @@ -21,7 +21,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; @@ -31,7 +31,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, uchar const *row_buffer_end, MY_BITMAP const *cols, diff --git a/sql/rpl_record_old.cc b/sql/rpl_record_old.cc index fa0c49b413c..5afa529a63c 100644 --- a/sql/rpl_record_old.cc +++ b/sql/rpl_record_old.cc @@ -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, "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, "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_rli.cc b/sql/rpl_rli.cc index 3a6bb4c33dc..ec6a16ddefa 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -57,13 +57,10 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery) #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), + abort_pos_wait(0), slave_run_id(0), sql_driver_thd(), inited(0), abort_slave(0), slave_running(0), until_condition(UNTIL_NONE), until_log_pos(0), retried_trans(0), executed_entries(0), - gtid_sub_id(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) + m_flags(0) { DBUG_ENTER("Relay_log_info::Relay_log_info"); @@ -88,12 +85,10 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery) &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; } @@ -106,14 +101,11 @@ Relay_log_info::~Relay_log_info() 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; } @@ -138,8 +130,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); @@ -529,6 +519,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 @@ -878,17 +870,54 @@ 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, event_relay_log_name); + if (cmp < 0) + { + group_relay_log_pos= event_relay_log_pos; + strmake_buf(group_relay_log_name, event_relay_log_name); + notify_group_relay_log_name_update(); + } else if (cmp == 0 && group_relay_log_pos < event_relay_log_pos) + group_relay_log_pos= event_relay_log_pos; - notify_group_relay_log_name_update(); + /* + 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); + notify_group_master_log_name_update(); + group_master_log_pos= log_pos; + } + else if (group_master_log_pos < log_pos) + group_master_log_pos= log_pos; + } + } + 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) // 3.23 binlogs don't have log_posx + group_master_log_pos= log_pos; + } /* If the slave does not support transactions and replicates a transaction, @@ -920,12 +949,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); @@ -941,6 +964,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. @@ -1012,7 +1038,7 @@ 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, !just_reset)) + if (rli->relay_log.reset_logs(thd, !just_reset, NULL, 0)) { *errmsg = "Failed during log reset"; error=1; @@ -1100,9 +1126,9 @@ bool Relay_log_info::is_until_satisfied(THD *thd, Log_event *ev) !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_pos= ((!ev)? group_master_log_pos : + (get_flag(IN_TRANSACTION) || !ev->log_pos) ? + group_master_log_pos : ev->log_pos - ev->data_written); } else { /* until_condition == UNTIL_RELAY_POS */ @@ -1195,19 +1221,24 @@ bool Relay_log_info::cached_charset_compare(char *charset) const void Relay_log_info::stmt_done(my_off_t event_master_log_pos, - time_t event_creation_time, THD *thd) + time_t event_creation_time, THD *thd, + rpl_group_info *rgi) { #ifndef DBUG_OFF extern uint debug_not_change_ts_if_art_event; #endif - clear_flag(IN_STMT); + 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 @@ -1225,12 +1256,13 @@ 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); - if (rpl_global_gtid_slave_state.record_and_update_gtid(thd, this)) + 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, "Failed to update GTID state in %s.%s, slave state may become " @@ -1245,7 +1277,8 @@ void Relay_log_info::stmt_done(my_off_t event_master_log_pos, */ } DBUG_EXECUTE_IF("inject_crash_before_flush_rli", DBUG_SUICIDE();); - flush_relay_log_info(this); + if (mi->using_gtid == Master_info::USE_GTID_NO) + flush_relay_log_info(this); DBUG_EXECUTE_IF("inject_crash_after_flush_rli", DBUG_SUICIDE();); /* Note that Rotate_log_event::do_apply_event() does not call this @@ -1259,127 +1292,10 @@ void Relay_log_info::stmt_done(my_off_t event_master_log_pos, IF_DBUG(debug_not_change_ts_if_art_event > 0, 1))) last_master_timestamp= event_creation_time; } -} - -#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -void Relay_log_info::cleanup_context(THD *thd, bool error) -{ - DBUG_ENTER("Relay_log_info::cleanup_context"); - - DBUG_ASSERT(sql_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 - maybe the Rows_log_event have not been found or will not be, because slave - SQL thread is stopping, or relay log has a missing tail etc). So we close - all thread's tables. And so the table mappings have to be cancelled. - 2) Rows_log_event::do_apply_event() may even have started statements or - transactions on them, which we need to rollback in case of error. - 3) If finding a Format_description_log_event after a BEGIN, we also need - to rollback before continuing with the next events. - 4) so we need this "context cleanup" function. - */ - if (error) - { - trans_rollback_stmt(thd); // if a "statement transaction" - trans_rollback(thd); // if a "real transaction" - } - m_table_map.clear_tables(); - slave_close_thread_tables(thd); - if (error) - thd->mdl_context.release_transactional_locks(); - clear_flag(IN_STMT); - /* - Cleanup for the flags that have been set at do_apply_event. - */ - thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS; - thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS; - - /* - Reset state related to long_find_row notes in the error log: - - timestamp - - flag that decides whether the slave prints or not - */ - reset_row_stmt_start_timestamp(); - unset_long_find_row_note_printed(); - - DBUG_VOID_RETURN; -} - -void Relay_log_info::clear_tables_to_lock() -{ - DBUG_ENTER("Relay_log_info::clear_tables_to_lock()"); -#ifndef DBUG_OFF - /** - When replicating in RBR and MyISAM Merge tables are involved - open_and_lock_tables (called in do_apply_event) appends the - base tables to the list of tables_to_lock. Then these are - removed from the list in close_thread_tables (which is called - before we reach this point). - - This assertion just confirms that we get no surprises at this - point. - */ - uint i=0; - for (TABLE_LIST *ptr= tables_to_lock ; ptr ; ptr= ptr->next_global, i++) ; - DBUG_ASSERT(i == tables_to_lock_count); -#endif - - while (tables_to_lock) - { - uchar* to_free= reinterpret_cast<uchar*>(tables_to_lock); - if (tables_to_lock->m_tabledef_valid) - { - tables_to_lock->m_tabledef.table_def::~table_def(); - tables_to_lock->m_tabledef_valid= FALSE; - } - - /* - If blob fields were used during conversion of field values - from the master table into the slave table, then we need to - free the memory used temporarily to store their values before - copying into the slave's table. - */ - if (tables_to_lock->m_conv_table) - free_blobs(tables_to_lock->m_conv_table); - - tables_to_lock= - static_cast<RPL_TABLE_LIST*>(tables_to_lock->next_global); - tables_to_lock_count--; - my_free(to_free); - } - DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0); DBUG_VOID_RETURN; } -void Relay_log_info::slave_close_thread_tables(THD *thd) -{ - DBUG_ENTER("Relay_log_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->get_stmt_da()->set_overwrite_status(false); - - close_thread_tables(thd); - /* - - If inside a multi-statement transaction, - defer the release of metadata locks until the current - transaction is either committed or rolled back. This prevents - other statements from modifying the table for the entire - duration of this transaction. This provides commit ordering - and guarantees serializability across multiple transactions. - - If in autocommit mode, or outside a transactional context, - automatically release metadata locks of the current statement. - */ - if (! thd->in_multi_stmt_transaction_mode()) - thd->mdl_context.release_transactional_locks(); - else - thd->mdl_context.release_statement_locks(); - - clear_tables_to_lock(); - DBUG_VOID_RETURN; -} - - +#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) int rpl_load_gtid_slave_state(THD *thd) { @@ -1409,7 +1325,7 @@ rpl_load_gtid_slave_state(THD *thd) goto end; array_inited= true; - mysql_reset_thd_for_next_command(thd, 0); + mysql_reset_thd_for_next_command(thd); tlist.init_one_table(STRING_WITH_LEN("mysql"), rpl_gtid_slave_state_table_name.str, @@ -1555,4 +1471,225 @@ end: DBUG_RETURN(err); } + +rpl_group_info::rpl_group_info(Relay_log_info *rli_) + : rli(rli_), thd(0), gtid_sub_id(0), wait_commit_sub_id(0), + wait_commit_group_info(0), wait_start_sub_id(0), parallel_entry(0), + deferred_events(NULL), m_annotate_event(0), tables_to_lock(0), + tables_to_lock_count(0), trans_retries(0), last_event_start_time(0), + is_parallel_exec(false), is_error(false), + row_stmt_start_timestamp(0), long_find_row_note_printed(false) +{ + 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.server_id= gev->server_id; + rgi->current_gtid.domain_id= gev->domain_id; + rgi->current_gtid.seq_no= gev->seq_no; + 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: + 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("Relay_log_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 + maybe the Rows_log_event have not been found or will not be, because slave + SQL thread is stopping, or relay log has a missing tail etc). So we close + all thread's tables. And so the table mappings have to be cancelled. + 2) Rows_log_event::do_apply_event() may even have started statements or + transactions on them, which we need to rollback in case of error. + 3) If finding a Format_description_log_event after a BEGIN, we also need + to rollback before continuing with the next events. + 4) so we need this "context cleanup" function. + */ + if (error) + { + trans_rollback_stmt(thd); // if a "statement transaction" + trans_rollback(thd); // if a "real transaction" + } + m_table_map.clear_tables(); + slave_close_thread_tables(thd); + if (error) + { + thd->mdl_context.release_transactional_locks(); + + 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); + } + } + + /* + Cleanup for the flags that have been set at do_apply_event. + */ + thd->variables.option_bits&= ~OPTION_NO_FOREIGN_KEY_CHECKS; + thd->variables.option_bits&= ~OPTION_RELAXED_UNIQUE_CHECKS; + + /* + Reset state related to long_find_row notes in the error log: + - timestamp + - flag that decides whether the slave prints or not + */ + reset_row_stmt_start_timestamp(); + unset_long_find_row_note_printed(); + + DBUG_VOID_RETURN; +} + + +void rpl_group_info::clear_tables_to_lock() +{ + DBUG_ENTER("Relay_log_info::clear_tables_to_lock()"); +#ifndef DBUG_OFF + /** + When replicating in RBR and MyISAM Merge tables are involved + open_and_lock_tables (called in do_apply_event) appends the + base tables to the list of tables_to_lock. Then these are + removed from the list in close_thread_tables (which is called + before we reach this point). + + This assertion just confirms that we get no surprises at this + point. + */ + uint i=0; + for (TABLE_LIST *ptr= tables_to_lock ; ptr ; ptr= ptr->next_global, i++) ; + DBUG_ASSERT(i == tables_to_lock_count); +#endif + + while (tables_to_lock) + { + uchar* to_free= reinterpret_cast<uchar*>(tables_to_lock); + if (tables_to_lock->m_tabledef_valid) + { + tables_to_lock->m_tabledef.table_def::~table_def(); + tables_to_lock->m_tabledef_valid= FALSE; + } + + /* + If blob fields were used during conversion of field values + from the master table into the slave table, then we need to + free the memory used temporarily to store their values before + copying into the slave's table. + */ + if (tables_to_lock->m_conv_table) + free_blobs(tables_to_lock->m_conv_table); + + tables_to_lock= + static_cast<RPL_TABLE_LIST*>(tables_to_lock->next_global); + tables_to_lock_count--; + my_free(to_free); + } + DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0); + DBUG_VOID_RETURN; +} + + +void rpl_group_info::slave_close_thread_tables(THD *thd) +{ + DBUG_ENTER("Relay_log_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->get_stmt_da()->set_overwrite_status(false); + + close_thread_tables(thd); + /* + - If inside a multi-statement transaction, + defer the release of metadata locks until the current + transaction is either committed or rolled back. This prevents + other statements from modifying the table for the entire + duration of this transaction. This provides commit ordering + and guarantees serializability across multiple transactions. + - If in autocommit mode, or outside a transactional context, + automatically release metadata locks of the current statement. + */ + if (! thd->in_multi_stmt_transaction_mode()) + thd->mdl_context.release_transactional_locks(); + else + thd->mdl_context.release_statement_locks(); + + clear_tables_to_lock(); + DBUG_VOID_RETURN; +} + + #endif diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index 6dd757343fd..ff2ffd0b366 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -22,6 +22,7 @@ #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; @@ -52,18 +53,20 @@ class Master_info; *****************************************************************************/ +struct rpl_group_info; + 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 }; /* @@ -128,9 +131,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; @@ -138,13 +146,13 @@ 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; @@ -161,8 +169,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. @@ -177,6 +185,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 */ @@ -208,18 +220,6 @@ public: */ bool sql_force_rotate_relay; - /* - 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). - */ -#if MYSQL_VERSION_ID < 40100 - ulonglong future_master_log_pos; -#else - ulonglong future_group_master_log_pos; -#endif - time_t last_master_timestamp; void clear_until_condition(); @@ -236,7 +236,13 @@ public: ulong max_relay_log_size; 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 @@ -284,14 +290,16 @@ public: 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 trans_retries, retried_trans; - ulong executed_entries; /* For SLAVE STATUS */ + ulong retried_trans; + /* + Number of executed events for SLAVE STATUS. + Protected by slave_executed_entries_lock + */ + int64 executed_entries; /* If the end of the hot relay log is made of master's events ignored by the @@ -303,6 +311,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 @@ -311,13 +321,7 @@ public: char slave_patternload_file[FN_REFLEN]; size_t slave_patternload_file_size; - /* - 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; + rpl_parallel parallel; Relay_log_info(bool is_slave_recovery); ~Relay_log_info(); @@ -341,13 +345,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, @@ -364,27 +364,6 @@ public: 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 @@ -394,52 +373,6 @@ public: 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. @@ -459,8 +392,28 @@ public: the <code>Seconds_behind_master</code> field. */ void stmt_done(my_off_t event_log_pos, - time_t event_creation_time, THD *thd); + time_t event_creation_time, THD *thd, + rpl_group_info *rgi); + + /** + 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. @@ -469,7 +422,7 @@ public: */ void set_flag(enum_state_flag flag) { - m_flags |= (1UL << flag); + m_flags|= flag; } /** @@ -481,7 +434,7 @@ public: */ bool get_flag(enum_state_flag flag) { - return m_flags & (1UL << flag); + return m_flags & flag; } /** @@ -491,23 +444,159 @@ public: */ void clear_flag(enum_state_flag flag) { - m_flags &= ~(1UL << flag); + m_flags&= ~flag; } - /** - Is the replication inside a group? +private: - 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 + /* + 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; +}; - @retval true Replication thread is currently inside a group - @retval false Replication thread is currently not inside a group + +/* + 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 +{ + 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; + /* + 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; + /* + If non-zero, the event group must wait for this sub_id to be committed + before the execution of the event group is allowed to start. + + (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 commtted). + */ + uint64 wait_start_sub_id; + + 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; + bool is_error; + +private: + /* + 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; +public: + + rpl_group_info(Relay_log_info *rli_); + ~rpl_group_info(); + + /* + 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 @@ -518,7 +607,7 @@ public: { free_annotate_event(); m_annotate_event= event; - sql_thd->variables.binlog_annotate_row_events= 1; + this->thd->variables.binlog_annotate_row_events= 1; } /** @@ -540,12 +629,33 @@ 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 *); + time_t get_row_stmt_start_timestamp() { return row_stmt_start_timestamp; @@ -579,18 +689,11 @@ public: return long_find_row_note_printed; } -private: - - 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; - - Annotate_rows_log_event *m_annotate_event; + inline void inc_event_relay_log_pos() + { + if (!is_parallel_exec) + rli->event_relay_log_pos= future_event_relay_log_pos; + } }; @@ -601,5 +704,8 @@ int init_relay_log_info(Relay_log_info* rli, const char* info_fname); extern struct rpl_slave_state rpl_global_gtid_slave_state; 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_utility.cc b/sql/rpl_utility.cc index db47c3c164a..590a5ae06ac 100644 --- a/sql/rpl_utility.cc +++ b/sql/rpl_utility.cc @@ -1183,21 +1183,21 @@ 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(rgi->deferred_events_collecting); - DBUG_ASSERT(rli->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; - return res; + rgi->deferred_events_collecting= true; + DBUG_RETURN(res); } void Deferred_log_events::rewind() diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h index 9ac17f68a1f..1a00a58d453 100644 --- a/sql/rpl_utility.h +++ b/sql/rpl_utility.h @@ -283,7 +283,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; }; }; diff --git a/sql/set_var.cc b/sql/set_var.cc index db74d8f0d9d..8ae29e01a20 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -829,26 +829,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); - } + user= get_current_user(thd, 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; @@ -869,6 +850,31 @@ int set_var_password::update(THD *thd) } /***************************************************************************** + Functions to handle SET ROLE +*****************************************************************************/ +int set_var_role::check(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + ulonglong access; + int status= acl_check_setrole(thd, base.str, &access); + save_result.ulonglong_value= access; + return status; +#else + return 0; +#endif +} + +int set_var_role::update(THD *thd) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + return acl_setrole(thd, base.str, save_result.ulonglong_value); +#else + return 0; +#endif +} + + +/***************************************************************************** Functions to handle SET NAMES and SET CHARACTER SET *****************************************************************************/ diff --git a/sql/set_var.h b/sql/set_var.h index dd01f3c6657..eebbf9b590f 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -279,6 +279,17 @@ public: int update(THD *thd); }; +/* For SET ROLE */ + +class set_var_role: public set_var +{ +public: + set_var_role(LEX_STRING role_arg) : + set_var(OPT_SESSION, NULL, &role_arg, NULL){}; + int check(THD *thd); + int update(THD *thd); +}; + /* For SET NAMES and SET CHARACTER SET */ diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 35f2cfb330c..3ddeb7d82bb 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -2853,29 +2853,29 @@ ER_UDF_EXISTS swe "Funktionen '%-.192s' finns redan" ukr "Функція '%-.192s' вже існує" ER_CANT_OPEN_LIBRARY - 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)" - 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 "共有ライブラリ '%-.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)" - pol "Can't open shared library '%-.192s' (errno: %d %-.128s)" - por "Não pode abrir biblioteca compartilhada '%-.192s' (erro no. %d '%-.128s')" - rum "Nu pot deschide libraria shared '%-.192s' (Eroare: %d %-.128s)" - rus "Невозможно открыть динамическую библиотеку '%-.192s' (ошибка: %d %-.128s)" - serbian "Ne mogu da otvorim share-ovanu biblioteku '%-.192s' (errno: %d %-.128s)" - slo "Nemôžem otvoriť zdieľanú knižnicu '%-.192s' (chybový kód: %d %-.128s)" - spa "No puedo abrir libraria conjugada '%-.192s' (errno: %d %-.128s)" - swe "Kan inte öppna det dynamiska biblioteket '%-.192s' (Felkod: %d %-.128s)" - ukr "Не можу відкрити розділювану бібліотеку '%-.192s' (помилка: %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)" + 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 "共有ライブラリ '%-.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)" + pol "Can't open shared library '%-.192s' (errno: %d, %-.128s)" + por "Não pode abrir biblioteca compartilhada '%-.192s' (erro no. %d, %-.128s)" + rum "Nu pot deschide libraria shared '%-.192s' (Eroare: %d, %-.128s)" + rus "Невозможно открыть динамическую библиотеку '%-.192s' (ошибка: %d, %-.128s)" + serbian "Ne mogu da otvorim share-ovanu biblioteku '%-.192s' (errno: %d, %-.128s)" + slo "Nemôžem otvoriť zdieľanú knižnicu '%-.192s' (chybový kód: %d, %-.128s)" + spa "No puedo abrir libraria conjugada '%-.192s' (errno: %d, %-.128s)" + swe "Kan inte öppna det dynamiska biblioteket '%-.192s' (Felkod: %d, %-.128s)" + ukr "Не можу відкрити розділювану бібліотеку '%-.192s' (помилка: %d, %-.128s)" ER_CANT_FIND_DL_ENTRY cze "Nemohu najít funkci '%-.128s' v knihovně" dan "Kan ikke finde funktionen '%-.128s' i bibliotek" @@ -2998,7 +2998,7 @@ 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 +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" @@ -5558,9 +5558,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" @@ -7022,7 +7021,7 @@ ER_SQL_DISCOVER_ERROR ER_FAILED_GTID_STATE_INIT eng "Failed initializing replication GTID state" ER_INCORRECT_GTID_STATE - eng "Could not parse GTID list for GTID_POS" + eng "Could not parse GTID list" ER_CANNOT_UPDATE_GTID_STATE eng "Could not update replication slave gtid state" ER_DUPLICATE_GTID_DOMAIN @@ -7050,3 +7049,29 @@ 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 errorneous 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" diff --git a/sql/signal_handler.cc b/sql/signal_handler.cc index 9437db6c318..c50072f5159 100644 --- a/sql/signal_handler.cc +++ b/sql/signal_handler.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., 51 Franklin St, Fifth Floor, Boston, MA 02111-1307 USA */ + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "my_global.h" #include <signal.h> diff --git a/sql/slave.cc b/sql/slave.cc index 8665cf646fc..f3ac16db110 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -60,6 +60,8 @@ #include "rpl_tblmap.h" #include "debug_sync.h" +#include "rpl_parallel.h" + #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") @@ -147,8 +149,8 @@ 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 bool io_slave_killed(Master_info* mi); +static bool sql_slave_killed(rpl_group_info *rgi); static int init_slave_thread(THD* thd, Master_info *mi, SLAVE_THD_TYPE thd_type); static void print_slave_skip_errors(void); @@ -157,14 +159,14 @@ 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 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 bool check_io_slave_killed(Master_info *mi, const char *info); static bool send_show_master_info_header(THD *thd, bool full, size_t gtid_pos_length); static bool send_show_master_info_data(THD *thd, Master_info *mi, bool full, @@ -395,6 +397,9 @@ int init_slave() goto err; } + if (global_rpl_thread_pool.init(opt_slave_parallel_threads)) + return 1; + /* If --slave-skip-errors=... was not used, the string value for the system variable has not been set up yet. Do it now. @@ -600,26 +605,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; - if (table->file != NULL) - { - /* - Since we are stealing opened temporary tables from one thread to another, - we need to let the performance schema know that, - for aggregates per thread to work properly. - */ - table->file->unbind_psi(); - table->file->rebind_psi(); - } - } -} - int terminate_slave_threads(Master_info* mi,int thread_mask,bool skip_lock) { DBUG_ENTER("terminate_slave_threads"); @@ -634,7 +619,7 @@ int terminate_slave_threads(Master_info* mi,int thread_mask,bool skip_lock) { DBUG_PRINT("info",("Terminating SQL thread")); mi->rli.abort_slave=1; - if ((error=terminate_slave_thread(mi->rli.sql_thd, sql_lock, + if ((error=terminate_slave_thread(mi->rli.sql_driver_thd, sql_lock, &mi->rli.stop_cond, &mi->rli.slave_running, skip_lock)) && @@ -997,17 +982,17 @@ void end_slave() master_info_index= 0; 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); } /** @@ -1023,26 +1008,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 " @@ -1052,25 +1047,33 @@ 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 " + "is_in_group: %d", + thd->transaction.all.modified_non_trans_table, + test(thd->variables.option_bits & OPTION_BEGIN), + 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", @@ -1093,7 +1096,8 @@ static bool sql_slave_killed(THD* thd, Relay_log_info* rli) else { ret= TRUE; - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, ER(ER_SLAVE_FATAL_ERROR), + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + ER(ER_SLAVE_FATAL_ERROR), msg_stopped); } } @@ -1103,7 +1107,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); } @@ -1505,7 +1509,7 @@ 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))) { @@ -1570,7 +1574,7 @@ not always make sense; please check the manual before using it)."; } 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))) { @@ -1643,7 +1647,7 @@ be equal for the Statement-format replication to work"; 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))) { @@ -1706,7 +1710,7 @@ be equal for the Statement-format replication to work"; 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))) { @@ -1751,7 +1755,7 @@ when it try to get the value of TIME_ZONE global variable from master."; sprintf(query, query_format, llbuf); if (mysql_real_query(mysql, query, strlen(query)) - && !check_io_slave_killed(mi->io_thd, mi, NULL)) + && !check_io_slave_killed(mi, NULL)) { errmsg= "The slave I/O thread stops because SET @master_heartbeat_period " "on master failed."; @@ -1786,7 +1790,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) @@ -1832,7 +1836,7 @@ when it try to get the value of TIME_ZONE global variable from master."; 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))) { @@ -2096,7 +2100,7 @@ after_set_capability: 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->io_thd, mi, NULL)) + else if (check_io_slave_killed(mi, NULL)) goto slave_killed_err; else if (is_network_error(mysql_errno(mysql))) { @@ -2162,7 +2166,7 @@ static bool wait_for_relay_log_space(Relay_log_info* rli) &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); @@ -2241,34 +2245,66 @@ 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, + ER(ER_SLAVE_CREATE_EVENT_FAILURE), + "Rotate_event (out of memory?)," + " SHOW SLAVE STATUS may be inaccurate"); + } + if (rli->ign_gtids.count()) + { + 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, + ER(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))) + 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, ER(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, + ER(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); @@ -2337,7 +2373,7 @@ 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), @@ -2512,8 +2548,15 @@ static bool send_show_master_info_data(THD *thd, Master_info *mi, bool full, &my_charset_bin); mysql_mutex_lock(&mi->run_lock); if (full) - protocol->store(mi->rli.sql_thd ? mi->rli.sql_thd->get_proc_info() : "", + { + /* + 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); @@ -2844,8 +2887,8 @@ static int init_slave_thread(THD* thd, Master_info *mi, @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; @@ -2859,7 +2902,7 @@ static inline bool slave_sleep(THD *thd, time_t seconds, mysql_mutex_lock(lock); 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) @@ -3064,19 +3107,21 @@ static int has_temporary_error(THD *thd) @retval 2 No error calling ev->apply_event(), but error calling ev->update_pos(). */ -int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) +int apply_event_and_update_pos(Log_event* ev, THD* thd, + rpl_group_info *rgi, + rpl_parallel_thread *rpt) { int exec_res= 0; - + Relay_log_info* rli= rgi->rli; DBUG_ENTER("apply_event_and_update_pos"); 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; 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)); + (ulong) rgi->last_event_start_time)); /* Execute the event to change the database and update the binary @@ -3117,15 +3162,21 @@ 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); + int reason= ev->shall_skip(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); + 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); #ifndef DBUG_OFF /* @@ -3141,9 +3192,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", + DBUG_PRINT("info", ("OPTION_BEGIN: %d IN_STMT: %d IN_TRANSACTION: %d", test(thd->variables.option_bits & OPTION_BEGIN), - rli->get_flag(Relay_log_info::IN_STMT))); + 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 @@ -3151,7 +3203,7 @@ 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 @@ -3187,12 +3239,94 @@ int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli) DBUG_RETURN(2); } } + else + { + /* + Make sure we do not errorneously update gtid_slave_pos with a lingering + GTID from this failed event group (MDEV-4906). + */ + rgi->gtid_sub_id= 0; + } DBUG_RETURN(exec_res ? 1 : 0); } /** + 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 @@ -3220,22 +3354,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; @@ -3244,6 +3379,7 @@ 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(); /* This tests if the position of the beginning of the current event @@ -3257,8 +3393,8 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) sql_print_information("Slave SQL thread stopped because it reached its" " UNTIL position %s", llstr(rli->until_pos(), buf)); /* - 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; mysql_mutex_unlock(&rli->data_lock); @@ -3273,56 +3409,49 @@ 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: - /* - 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)) - 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; + /* + Execute queries in parallel, except if slave_skip_counter is set, + as it's is easier to skip queries in single threaded mode. + */ + + if (opt_slave_parallel_threads > 0 && rli->slave_skip_counter == 0) + DBUG_RETURN(rli->parallel.do_event(serial_rgi, ev, event_size)); + + /* + 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 (typ == GTID_EVENT && + event_group_new_gtid(serial_rgi, static_cast<Gtid_log_event *>(ev))) + { + 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); } + 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, NULL); + + delete_or_keep_event_post_apply(serial_rgi, typ, ev); /* update_log_pos failed: this should not happen, so we don't @@ -3345,14 +3474,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"); @@ -3365,17 +3496,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, MY_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 @@ -3394,11 +3527,13 @@ 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)); } } + thread_safe_increment64(&rli->executed_entries, + &slave_executed_entries_lock); DBUG_RETURN(exec_res); } mysql_mutex_unlock(&rli->data_lock); @@ -3416,9 +3551,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); @@ -3469,7 +3604,7 @@ 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) @@ -3506,7 +3641,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]); @@ -3682,11 +3817,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; @@ -3701,7 +3839,7 @@ connected: 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"); @@ -3726,13 +3864,13 @@ connected: } DBUG_PRINT("info",("Starting reading binary log from master")); - while (!io_slave_killed(thd,mi)) + while (!io_slave_killed(mi)) { 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 \ + if (check_io_slave_killed(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])) @@ -3752,7 +3890,7 @@ requesting master dump") || const char *event_buf; DBUG_ASSERT(mi->last_error().number == 0); - while (!io_slave_killed(thd,mi)) + while (!io_slave_killed(mi)) { ulong event_len; /* @@ -3763,7 +3901,7 @@ requesting master dump") || */ 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 \ + if (check_io_slave_killed(mi, "Slave I/O thread killed while \ reading event")) goto err; DBUG_EXECUTE_IF("FORCE_SLAVE_TO_RECONNECT_EVENT", @@ -3841,7 +3979,8 @@ Stopping slave I/O thread due to out-of-memory error from master"); 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; @@ -3853,10 +3992,11 @@ 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 { @@ -3917,6 +4057,8 @@ err: mi->mysql=0; } write_ignored_events_info_to_relay_log(thd, mi); + 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); mysql_mutex_lock(&mi->run_lock); @@ -3944,6 +4086,9 @@ err_during_init: DBUG_LEAVE; // Must match DBUG_ENTER() my_thread_end(); +#ifdef HAVE_OPENSSL + ERR_remove_state(0); +#endif pthread_exit(0); return 0; // Avoid compiler warnings } @@ -4014,6 +4159,93 @@ end: } +void +slave_output_error_info(Relay_log_info *rli, 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 + */ + uint32 const last_errno= rli->last_error().number; + char llbuff[22]; + + 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(), "%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) + { + String 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 %s%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, + llbuff), tmp.c_ptr_safe()); + } + else + { + String 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 %s%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, llbuff), + tmp.c_ptr_safe()); + } +} + + /** Slave SQL thread entry point. @@ -4034,6 +4266,7 @@ pthread_handler_t handle_slave_sql(void *arg) Master_info *mi= ((Master_info*)arg); Relay_log_info* rli = &mi->rli; const char *errmsg; + rpl_group_info *serial_rgi; // needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff my_thread_init(); @@ -4042,6 +4275,7 @@ pthread_handler_t handle_slave_sql(void *arg) LINT_INIT(saved_master_log_pos); LINT_INIT(saved_log_pos); + 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->rpl_filter = mi->rpl_filter; @@ -4055,7 +4289,15 @@ 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++; @@ -4074,14 +4316,12 @@ pthread_handler_t handle_slave_sql(void *arg) goto err_during_init; } thd->init_for_queries(); - thd->rli_slave= rli; - if ((rli->deferred_events_collecting= mi->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 @@ -4114,14 +4354,14 @@ 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 mysql_mutex_lock(&rli->log_space_lock); rli->ignore_log_space_limit= 0; mysql_mutex_unlock(&rli->log_space_lock); - rli->trans_retries= 0; // start from "no error" - DBUG_PRINT("info", ("rli->trans_retries: %lu", rli->trans_retries)); + serial_rgi->gtid_sub_id= 0; if (init_relay_log_pos(rli, rli->group_relay_log_name, rli->group_relay_log_pos, @@ -4132,6 +4372,7 @@ pthread_handler_t handle_slave_sql(void *arg) "Error initializing relay log position: %s", errmsg); goto err; } + strcpy(rli->future_event_master_log_name, rli->group_master_log_name); THD_CHECK_SENTRY(thd); #ifndef DBUG_OFF { @@ -4157,7 +4398,6 @@ pthread_handler_t handle_slave_sql(void *arg) #endif } #endif - DBUG_ASSERT(rli->sql_thd == thd); #ifdef WITH_WSREP thd->wsrep_exec_mode= LOCAL_STATE; @@ -4246,10 +4486,9 @@ log '%s' at position %s, relay log '%s' position: %s%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_STAGE_INFO(thd, stage_reading_event_from_the_relay_log); - DBUG_ASSERT(rli->sql_thd == thd); THD_CHECK_SENTRY(thd); if (saved_skip && rli->slave_skip_counter == 0) @@ -4266,98 +4505,19 @@ log '%s' at position %s, relay log '%s' position: %s%s", RPL_LOG_NAME, saved_skip= 0; } - if (exec_relay_log_event(thd,rli)) + if (exec_relay_log_event(thd, rli, serial_rgi)) { 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)) - { - /* - 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->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(), "%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) - { - String 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_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%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, - llbuff), tmp.c_ptr_safe()); - } - else - { - String 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_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%s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, llbuff), - tmp.c_ptr_safe()); - } - } + if (!sql_slave_killed(serial_rgi)) + slave_output_error_info(rli, thd); goto err; } - rli->executed_entries++; } + if (opt_slave_parallel_threads > 0) + rli->parallel.wait_for_done(); + /* Thread stopped. Print the current replication position to the log */ { String tmp; @@ -4377,13 +4537,21 @@ the slave SQL thread with \"SLAVE START\". We stopped at log \ err: /* + Once again, in case we aborted with an error and skipped the first one. + (We want the first one to be before the printout of stop position to + get the correct position printed.) + */ + if (opt_slave_parallel_threads > 0) + rli->parallel.wait_for_done(); + + /* Some events set some playgrounds, which won't be cleared because thread stops. Stopping of this thread may not be known to these events ("stop" request is detected only by the present function, not by events), so we 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 @@ -4392,6 +4560,8 @@ 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) + flush_relay_log_info(rli); THD_STAGE_INFO(thd, stage_waiting_for_slave_mutex_on_exit); mysql_mutex_lock(&rli->run_lock); err_during_init: @@ -4410,19 +4580,18 @@ err_during_init: 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; /* 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(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 + serial_rgi->thd= rli->sql_driver_thd= 0; mysql_mutex_lock(&LOCK_thread_count); THD_CHECK_SENTRY(thd); + thd->rgi_fake= thd->rgi_slave= NULL; + delete serial_rgi; delete thd; mysql_mutex_unlock(&LOCK_thread_count); /* @@ -4436,6 +4605,9 @@ err_during_init: DBUG_LEAVE; // Must match DBUG_ENTER() my_thread_end(); +#ifdef HAVE_OPENSSL + ERR_remove_state(0); +#endif pthread_exit(0); return 0; // Avoid compiler warnings } @@ -4848,6 +5020,8 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) ulong s_id; bool unlock_data_lock= TRUE; bool gtid_skip_enqueue= false; + bool got_gtid_event= false; + rpl_gtid event_gtid; /* FD_q must have been prepared for the first R_a event @@ -4911,8 +5085,6 @@ 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)); @@ -5164,8 +5336,17 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) case GTID_EVENT: { - uchar dummy_flag; + 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)) @@ -5173,8 +5354,6 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) mi->gtid_event_seen= true; if (mi->gtid_reconnect_event_skip_count) { - rpl_gtid gtid; - /* 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 @@ -5183,21 +5362,14 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) 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, one the same IP + 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 (Gtid_log_event::peek(buf, event_len, checksum_alg, - >id.domain_id, >id.server_id, - >id.seq_no, &dummy_flag)) - { - error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; - goto err; - } - if (gtid.domain_id != mi->last_queued_gtid.domain_id || - gtid.server_id != mi->last_queued_gtid.server_id || - gtid.seq_no != mi->last_queued_gtid.seq_no) + 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; @@ -5207,7 +5379,7 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) &first); error_msg.append(STRING_WITH_LEN(", received: ")); first= true; - rpl_slave_state_tostring_helper(&error_msg, >id, &first); + rpl_slave_state_tostring_helper(&error_msg, &event_gtid, &first); goto err; } } @@ -5221,21 +5393,20 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) /* 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; } - if (Gtid_log_event::peek(buf, event_len, checksum_alg, - &mi->last_queued_gtid.domain_id, - &mi->last_queued_gtid.server_id, - &mi->last_queued_gtid.seq_no, &dummy_flag)) - { - error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; - goto err; - } + mi->last_queued_gtid= event_gtid; + mi->last_queued_gtid_standalone= + (gtid_flag & Gtid_log_event::FL_STANDALONE) != 0; ++mi->events_queued_since_last_gtid; + inc_pos= event_len; } break; @@ -5287,6 +5458,49 @@ 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); + /* + 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) || /* @@ -5329,6 +5543,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", @@ -5336,16 +5552,7 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) } else { - /* - 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; - } - else if (likely(!(rli->relay_log.appendv(buf,event_len,0)))) + if (likely(!(rli->relay_log.appendv(buf,event_len,0)))) { mi->master_log_pos+= inc_pos; DBUG_PRINT("info", ("master_log_pos: %lu", (ulong) mi->master_log_pos)); @@ -5356,11 +5563,33 @@ 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; + } + skip_relay_logging: err: @@ -5520,7 +5749,7 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, "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)) @@ -5598,19 +5827,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; } - + thd= mi->io_thd; if (!mysql) { if(!(mysql= mysql_init(NULL))) @@ -5653,11 +5883,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)); @@ -5667,6 +5897,7 @@ MYSQL *rpl_connect_master(MYSQL *mysql) } return mysql; } +#endif /* Store the file and position where the execute-slave thread are in the @@ -5772,17 +6003,21 @@ 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; + THD *thd = rgi->thd; DBUG_ENTER("next_event"); - DBUG_ASSERT(thd != 0); + DBUG_ASSERT(thd != 0 && thd == rli->sql_driver_thd); + *event_size= 0; #ifndef DBUG_OFF if (abort_slave_event_count && !rli->events_till_abort--) @@ -5798,7 +6033,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: @@ -5811,6 +6046,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))) { @@ -5846,7 +6082,8 @@ static Log_event* next_event(Relay_log_info* rli) llstr(my_b_tell(cur_log),llbuf1), llstr(rli->event_relay_log_pos,llbuf2))); 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(opt_slave_parallel_threads > 0 || + my_b_tell(cur_log) == rli->event_relay_log_pos); } #endif /* @@ -5861,43 +6098,23 @@ 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); - /* - 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 (ev->get_type_code() == GTID_EVENT) - { - Gtid_log_event *gev= static_cast<Gtid_log_event *>(ev); - uint64 sub_id= rpl_global_gtid_slave_state.next_sub_id(gev->domain_id); - if (!sub_id) - { - errmsg = "slave SQL thread aborted because of out-of-memory error"; - if (hot_log) - mysql_mutex_unlock(log_lock); - goto err; - } - rli->gtid_sub_id= sub_id; - rli->current_gtid.server_id= gev->server_id; - rli->current_gtid.domain_id= gev->domain_id; - rli->current_gtid.seq_no= gev->seq_no; - } + *event_size= rli->future_event_relay_log_pos - old_pos; if (hot_log) mysql_mutex_unlock(log_lock); 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) @@ -5962,6 +6179,25 @@ static Log_event* next_event(Relay_log_info* rli) DBUG_RETURN(ev); } + if (rli->ign_gtids.count()) + { + /* We generate and return a Gtid_list, to update gtid_slave_pos. */ + 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 can, and should release data_lock while we are waiting for update. If we do not, show slave status will block @@ -5985,14 +6221,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); @@ -6030,7 +6267,7 @@ static Log_event* next_event(Relay_log_info* rli) mysql_mutex_unlock(&rli->log_space_lock); mysql_cond_broadcast(&rli->log_space_cond); // 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; @@ -6363,10 +6600,10 @@ bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report, */ bool rpl_master_erroneous_autoinc(THD *thd) { - if (thd->rli_slave) + if (thd->rgi_slave) { DBUG_EXECUTE_IF("simulate_bug33029", return TRUE;); - return rpl_master_has_bug(thd->rli_slave, 33029, FALSE, NULL, NULL); + return rpl_master_has_bug(thd->rgi_slave->rli, 33029, FALSE, NULL, NULL); } return FALSE; } diff --git a/sql/slave.h b/sql/slave.h index 565f40b7236..3981a9d4f2c 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -51,6 +51,7 @@ class Relay_log_info; class Master_info; class Master_info_index; +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, @@ -227,9 +228,12 @@ int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset, void set_slave_thread_options(THD* thd); void set_slave_thread_default_charset(THD *thd, Relay_log_info const *rli); int rotate_relay_log(Master_info* mi); -int apply_event_and_update_pos(Log_event* ev, THD* thd, Relay_log_info* rli); +int apply_event_and_update_pos(Log_event* ev, THD* thd, + struct rpl_group_info *rgi, + rpl_parallel_thread *rpt); pthread_handler_t handle_slave_io(void *arg); +void slave_output_error_info(Relay_log_info *rli, THD *thd); pthread_handler_t handle_slave_sql(void *arg); bool net_request_file(NET* net, const char* fname); diff --git a/sql/sp.cc b/sql/sp.cc index ca8e41282cf..d3d692b8251 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -50,7 +50,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); static const @@ -548,6 +549,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", @@ -657,9 +662,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 @@ -804,7 +819,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; @@ -814,22 +830,12 @@ 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); - defstr.set_charset(creation_ctx->get_client_cs()); /* @@ -845,7 +851,7 @@ db_load_routine(THD *thd, stored_procedure_type type, 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; @@ -895,7 +901,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(); @@ -974,7 +980,8 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) { int ret; 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; @@ -1011,8 +1018,7 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) 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) { @@ -1087,7 +1093,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(); @@ -1167,6 +1173,9 @@ sp_create_routine(THD *thd, stored_procedure_type type, sp_head *sp) ret= SP_OK; if (table->file->ha_write_row(table->record[0])) ret= SP_WRITE_ROW_FAILED; + /* Make change permanent and avoid 'table is marked as crashed' errors */ + table->file->extra(HA_EXTRA_FLUSH); + if (ret == SP_OK) sp_cache_invalidate(); @@ -1256,6 +1265,8 @@ sp_drop_routine(THD *thd, stored_procedure_type type, sp_name *name) { if (table->file->ha_delete_row(table->record[0])) ret= SP_DELETE_ROW_FAILED; + /* Make change permanent and avoid 'table is marked as crashed' errors */ + table->file->extra(HA_EXTRA_FLUSH); } if (ret == SP_OK) @@ -1366,6 +1377,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) @@ -1540,7 +1553,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(); @@ -1648,7 +1665,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; @@ -1685,8 +1701,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); @@ -1694,7 +1708,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) { @@ -2255,7 +2270,7 @@ sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db, return sp; } #ifdef WITH_WSREP -int wsrep_create_sp(THD *thd, uchar** buf, int* buf_len) +int wsrep_create_sp(THD *thd, uchar** buf, size_t* buf_len) { String log_query; sp_head *sp = thd->lex->sphead; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 510bce21093..4bf6c13a7d8 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -282,9 +282,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: @@ -292,6 +295,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: @@ -312,6 +316,33 @@ 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 two exceptions: + - DELETE ... RETURNING + - EXPLAIN DELETE ... + */ + if (lex->select_lex.item_list.is_empty() && !lex->describe) + 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) + flags= 0; + else + flags= sp_head::MULTI_RESULTS; + break; + } default: flags= 0; break; @@ -2441,8 +2472,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); } @@ -2933,6 +2969,8 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, else if (! thd->in_sub_stmt) 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) { @@ -3066,7 +3104,15 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) log_slow_statement(thd); } else + { + /* change statistics */ + enum_sql_command save_sql_command= thd->lex->sql_command; + thd->lex->sql_command= SQLCOM_SELECT; + status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]); + thd->update_stats(); + thd->lex->sql_command= save_sql_command; *nextp= m_ip+1; + } thd->set_query(query_backup); thd->query_name_consts= 0; @@ -3154,6 +3200,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; diff --git a/sql/spatial.cc b/sql/spatial.cc index a01d2c59a49..b82e6977f8a 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -447,19 +447,18 @@ const char *Geometry::append_points(String *txt, uint32 n_points, const char *Geometry::get_mbr_for_points(MBR *mbr, const char *data, uint offset) const { - uint32 n_points; + uint32 points; /* read number of points */ if (no_data(data, 4)) return 0; - n_points= uint4korr(data); + points= uint4korr(data); data+= 4; - if (n_points > max_n_points || - no_data(data, (POINT_DATA_SIZE + offset) * n_points)) + if (not_enough_points(data, points, offset)) return 0; /* Calculate MBR for points */ - while (n_points--) + while (points--) { data+= offset; mbr->add_xy(data, data + SIZEOF_STORED_DOUBLE); @@ -481,7 +480,7 @@ bool Gis_point::init_from_wkt(Gis_read_stream *trs, String *wkb) { double x, y; if (trs->get_next_number(&x) || trs->get_next_number(&y) || - wkb->reserve(POINT_DATA_SIZE)) + wkb->reserve(POINT_DATA_SIZE, 512)) return 1; wkb->q_append(x); wkb->q_append(y); @@ -563,12 +562,16 @@ const Geometry::Class_info *Gis_point::get_class_info() const uint32 Gis_line_string::get_data_size() const { - uint32 n_points, size; - if (no_data(m_data, 4) || - (n_points= uint4korr(m_data)) > max_n_points || - no_data(m_data, (size= 4 + n_points * POINT_DATA_SIZE))) + uint32 n_points; + if (no_data(m_data, 4)) return GET_SIZE_ERROR; - return size; + + n_points= uint4korr(m_data); + + if (not_enough_points(m_data + 4, n_points)) + return GET_SIZE_ERROR; + + return 4 + n_points * POINT_DATA_SIZE; } @@ -607,9 +610,8 @@ uint Gis_line_string::init_from_wkb(const char *wkb, uint len, const char *wkb_end; Gis_point p; - if (len < 4 || - (n_points= wkb_get_uint(wkb, bo)) < 1 || - n_points > max_n_points) + if (len < 4 || (n_points= wkb_get_uint(wkb, bo)) < 1 || + ((len - 4) / POINT_DATA_SIZE) < n_points) return 0; proper_length= 4 + n_points * POINT_DATA_SIZE; @@ -638,8 +640,8 @@ bool Gis_line_string::get_data_as_wkt(String *txt, const char **end) const n_points= uint4korr(data); data += 4; - if (n_points < 1 || n_points > max_n_points || - no_data(data, POINT_DATA_SIZE * n_points) || + if (n_points < 1 || + not_enough_points(data, n_points) || txt->reserve(((MAX_DIGITS_IN_DOUBLE + 1)*2 + 1) * n_points)) return 1; @@ -676,8 +678,7 @@ int Gis_line_string::geom_length(double *len, const char **end) const return 1; n_points= uint4korr(data); data+= 4; - if (n_points < 1 || n_points > max_n_points || - no_data(data, POINT_DATA_SIZE * n_points)) + if (n_points < 1 || not_enough_points(data, n_points)) return 1; get_point(&prev_x, &prev_y, data); @@ -725,8 +726,7 @@ int Gis_line_string::is_closed(int *closed) const return 0; } data+= 4; - if (n_points == 0 || n_points > max_n_points || - no_data(data, POINT_DATA_SIZE * n_points)) + if (n_points == 0 || not_enough_points(data, n_points)) return 1; /* Get first point */ @@ -761,8 +761,7 @@ int Gis_line_string::end_point(String *result) const if (no_data(m_data, 4)) return 1; n_points= uint4korr(m_data); - if (n_points == 0 || n_points > max_n_points || - no_data(m_data, POINT_DATA_SIZE * n_points)) + if (n_points == 0 || not_enough_points(m_data+4, n_points)) return 1; return create_point(result, m_data + 4 + (n_points - 1) * POINT_DATA_SIZE); } @@ -775,9 +774,7 @@ int Gis_line_string::point_n(uint32 num, String *result) const return 1; num--; n_points= uint4korr(m_data); - if (num >= n_points || - num > max_n_points || // means (num > n_points || num < 1) - no_data(m_data, num * POINT_DATA_SIZE)) + if (num >= n_points || not_enough_points(m_data+4, n_points)) return 1; return create_point(result, m_data + 4 + num*POINT_DATA_SIZE); @@ -796,8 +793,7 @@ int Gis_line_string::store_shapes(Gcalc_shape_transporter *trn) const return 1; n_points= uint4korr(data); data+= 4; - if (n_points < 1 || n_points > max_n_points || - no_data(data, POINT_DATA_SIZE * n_points)) + if (n_points < 1 || not_enough_points(data, n_points)) return 1; trn->start_line(); @@ -840,7 +836,7 @@ uint32 Gis_polygon::get_data_size() const while (n_linear_rings--) { if (no_data(data, 4) || - (n_points= uint4korr(data)) > max_n_points) + not_enough_points(data+4, n_points= uint4korr(data))) return GET_SIZE_ERROR; data+= 4 + n_points*POINT_DATA_SIZE; } @@ -986,7 +982,7 @@ bool Gis_polygon::get_data_as_wkt(String *txt, const char **end) const return 1; n_points= uint4korr(data); data+= 4; - if (n_points > max_n_points || no_data(data, POINT_DATA_SIZE * n_points) || + if (not_enough_points(data, n_points) || txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points)) return 1; txt->qs_append('('); @@ -1040,8 +1036,8 @@ int Gis_polygon::area(double *ar, const char **end_of_data) const if (no_data(data, 4)) return 1; n_points= uint4korr(data); - if (n_points == 0 || n_points > max_n_points || - no_data(data, POINT_DATA_SIZE * n_points)) + if (n_points == 0 || + not_enough_points(data, n_points)) return 1; get_point(&prev_x, &prev_y, data+4); data+= (4+POINT_DATA_SIZE); @@ -1077,8 +1073,7 @@ int Gis_polygon::exterior_ring(String *result) const n_points= uint4korr(data); data+= 4; length= n_points * POINT_DATA_SIZE; - if (n_points > max_n_points || - no_data(data, length) || result->reserve(1 + 4 + 4 + length)) + if (not_enough_points(data, n_points) || result->reserve(1+4+4+ length)) return 1; result->q_append((char) wkb_ndr); @@ -1124,8 +1119,7 @@ int Gis_polygon::interior_ring_n(uint32 num, String *result) const n_points= uint4korr(data); points_size= n_points * POINT_DATA_SIZE; data+= 4; - if (n_points > max_n_points || - no_data(data, points_size) || result->reserve(1 + 4 + 4 + points_size)) + if (not_enough_points(data, n_points) || result->reserve(1+4+4+ points_size)) return 1; result->q_append((char) wkb_ndr); @@ -1162,8 +1156,7 @@ int Gis_polygon::centroid_xy(double *x, double *y) const return 1; org_n_points= n_points= uint4korr(data); data+= 4; - if (n_points == 0 || n_points > max_n_points || - no_data(data, POINT_DATA_SIZE * n_points)) + if (n_points == 0 || not_enough_points(data, n_points)) return 1; get_point(&prev_x, &prev_y, data); data+= POINT_DATA_SIZE; @@ -1237,8 +1230,7 @@ int Gis_polygon::store_shapes(Gcalc_shape_transporter *trn) const return 1; n_points= uint4korr(data); data+= 4; - if (!n_points || n_points > max_n_points || - no_data(data, POINT_DATA_SIZE * n_points)) + if (!n_points || not_enough_points(data, n_points)) return 1; trn->start_ring(); @@ -1292,13 +1284,12 @@ const Geometry::Class_info *Gis_polygon::get_class_info() const uint32 Gis_multi_point::get_data_size() const { uint32 n_points; - uint32 size; if (no_data(m_data, 4) || - (n_points= uint4korr(m_data)) > max_n_points || - no_data(m_data, (size= 4 + n_points*(POINT_DATA_SIZE + WKB_HEADER_SIZE)))) + not_enough_points(m_data+4, (n_points= uint4korr(m_data)), + WKB_HEADER_SIZE)) return GET_SIZE_ERROR; - return size; + return 4 + n_points * (POINT_DATA_SIZE + WKB_HEADER_SIZE); } @@ -1393,7 +1384,7 @@ bool Gis_multi_point::get_data_as_wkt(String *txt, const char **end) const n_points= uint4korr(m_data); if (n_points > max_n_points || - no_data(m_data+4, n_points * (POINT_DATA_SIZE + WKB_HEADER_SIZE)) || + not_enough_points(m_data+4, n_points, WKB_HEADER_SIZE) || txt->reserve(((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points)) return 1; *end= append_points(txt, n_points, m_data+4, WKB_HEADER_SIZE); @@ -1485,7 +1476,8 @@ uint32 Gis_multi_line_string::get_data_size() const while (n_line_strings--) { if (no_data(data, WKB_HEADER_SIZE + 4) || - (n_points= uint4korr(data + WKB_HEADER_SIZE)) > max_n_points) + not_enough_points(data + WKB_HEADER_SIZE+4, + (n_points= uint4korr(data + WKB_HEADER_SIZE)))) return GET_SIZE_ERROR; data+= (WKB_HEADER_SIZE + 4 + n_points*POINT_DATA_SIZE); } @@ -1614,7 +1606,7 @@ bool Gis_multi_line_string::get_data_as_wkt(String *txt, return 1; n_points= uint4korr(data + WKB_HEADER_SIZE); data+= WKB_HEADER_SIZE + 4; - if (n_points > max_n_points || no_data(data, n_points * POINT_DATA_SIZE) || + if (not_enough_points(data, n_points) || txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points)) return 1; txt->qs_append('('); @@ -1675,7 +1667,7 @@ int Gis_multi_line_string::geometry_n(uint32 num, String *result) const return 1; n_points= uint4korr(data + WKB_HEADER_SIZE); length= WKB_HEADER_SIZE + 4+ POINT_DATA_SIZE * n_points; - if (n_points > max_n_points || no_data(data, length)) + if (not_enough_points(data+WKB_HEADER_SIZE+4, n_points)) return 1; if (!--num) break; @@ -1806,7 +1798,7 @@ uint32 Gis_multi_polygon::get_data_size() const while (n_linear_rings--) { if (no_data(data, 4) || - (n_points= uint4korr(data)) > max_n_points) + not_enough_points(data+4, (n_points= uint4korr(data)))) return GET_SIZE_ERROR; data+= 4 + n_points * POINT_DATA_SIZE; } @@ -1940,8 +1932,7 @@ bool Gis_multi_polygon::get_data_as_wkt(String *txt, const char **end) const return 1; uint32 n_points= uint4korr(data); data+= 4; - if (n_points > max_n_points || - no_data(data, POINT_DATA_SIZE * n_points) || + if (not_enough_points(data, n_points) || txt->reserve(2 + ((MAX_DIGITS_IN_DOUBLE + 1) * 2 + 1) * n_points, 512)) return 1; @@ -2024,7 +2015,7 @@ int Gis_multi_polygon::geometry_n(uint32 num, String *result) const if (no_data(data, 4)) return 1; n_points= uint4korr(data); - if (n_points > max_n_points) + if (not_enough_points(data + 4, n_points)) return 1; data+= 4 + POINT_DATA_SIZE * n_points; } diff --git a/sql/spatial.h b/sql/spatial.h index b0e4b83bf6a..3a6055add06 100644 --- a/sql/spatial.h +++ b/sql/spatial.h @@ -214,11 +214,6 @@ struct Geometry_buffer; class Geometry { public: - // Maximum number of points in feature that can fit into String - static const uint32 max_n_points= - (uint32) (INT_MAX32 - WKB_HEADER_SIZE - 4 /* n_points */) / - POINT_DATA_SIZE; - Geometry() {} /* Remove gcc warning */ virtual ~Geometry() {} /* Remove gcc warning */ static void *operator new(size_t size, void *buffer) diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index d741ae67d23..7c2846c827c 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -44,13 +44,13 @@ #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 "sql_connect.h" #include "hostname.h" #include "sql_db.h" #include "sql_array.h" +#include "sql_hset.h" #include "sql_plugin_compat.h" @@ -59,15 +59,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(") }, @@ -176,19 +176,40 @@ 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 static plugin_ref native_password_plugin; +static char *safe_str(char *str) +{ return str ? str : const_cast<char*>(""); } + +static const char *safe_str(const char *str) +{ return str ? str : ""; } + /* Classes */ struct acl_host_and_ip @@ -197,6 +218,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,15 +239,27 @@ 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; @@ -232,7 +271,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 +283,55 @@ 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); + 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 = 0) + { + 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,30 @@ 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 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,11 +372,11 @@ 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; @@ -287,16 +388,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 +404,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 +425,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 +459,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 +485,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 +531,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 +573,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 +603,81 @@ 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) +{ + if (!this) + return true; + + size_t uname_l = username ? strlen(username) : 0; + size_t hname_l = hostname ? strlen(hostname) : 0; + size_t rname_l = rolename ? strlen(rolename) : 0; + /* + 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,7 +700,28 @@ static uchar* acl_entry_get_key(acl_entry *entry, size_t *length, #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ #define NORMAL_HANDSHAKE_SIZE 6 +#define ROLE_ASSIGN_COLUMN_IDX 43 +/* 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_dbs, acl_proxy_users; +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 mem, memex; static bool initialized=0; static bool allow_all_hosts=1; @@ -551,38 +730,101 @@ static DYNAMIC_ARRAY acl_wild_hosts; static hash_filo *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 *thd, TABLE *table, const char *host, const char *user, const char *new_password, uint new_password_len); static my_bool acl_load(THD *thd, TABLE_LIST *tables); static my_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 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 }; +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 (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)); +} + /* - Convert scrambled password to binary form, according to scramble type, + Convert scrambled password to binary form, according to scramble type, Binary form is stored in user.salt. */ -static -void +static void set_user_salt(ACL_USER *acl_user, const char *password, uint password_len) { if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH) @@ -637,11 +879,20 @@ 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; } +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. @@ -711,10 +962,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: @@ -725,8 +975,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; } } @@ -739,8 +989,9 @@ 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 @@ -762,8 +1013,6 @@ 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, MYF(0)); (void) my_init_dynamic_array(&acl_hosts,sizeof(ACL_HOST), 20, 50, MYF(0)); if (tables[0].table) // "host" table may not exist (e.g. in MySQL 5.6.7+) @@ -795,8 +1044,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.", - host.host.hostname ? host.host.hostname : "", - host.db ? host.db : ""); + host.host.hostname, host.db); } host.access= get_access(table,2); host.access= fix_rights_for_db(host.access); @@ -805,8 +1053,8 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) { sql_print_warning("'host' entry '%s|%s' " "ignored in --skip-name-resolve mode.", - host.host.hostname ? host.host.hostname : "", - host.db ? host.db : ""); + safe_str(host.host.hostname), + safe_str(host.db)); continue; } #ifndef TO_BE_REMOVED @@ -829,6 +1077,10 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) goto end; table->use_all_columns(); (void) my_init_dynamic_array(&acl_users,sizeof(ACL_USER), 50, 100, MYF(0)); + (void) my_hash_init2(&acl_roles,50, &my_charset_utf8_bin, + 0,0,0, (my_hash_get_key) acl_role_get_key, + (void (*)(void *))free_acl_role, 0); + username_char_length= MY_MIN(table->field[1]->char_length(), USERNAME_CHAR_LENGTH); password_length= table->field[2]->field_length / @@ -876,25 +1128,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)) + char *username= get_field(&mem, table->field[1]); + user.user.str= username; + user.user.length= username? strlen(username) : 0; + + /* + 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*>(""); + 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; { @@ -936,7 +1205,7 @@ 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.sort= get_sort(2, user.host.hostname, user.user.str); user.hostname_length= (user.host.hostname ? (uint) strlen(user.host.hostname) : 0); @@ -974,7 +1243,7 @@ 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++]); @@ -987,12 +1256,11 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) 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)); } - 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.str= + safe_str(get_field(&mem, table->field[next_field++])); user.auth_string.length= strlen(user.auth_string.str); fix_user_plugin_ptr(&user); @@ -1016,7 +1284,26 @@ 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)); + + if (is_role) + { + DBUG_PRINT("info", ("Found role %s", user.user.str)); + ACL_ROLE *entry= new (&mem) ACL_ROLE(&user, &mem); + 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 @@ -1035,25 +1322,27 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) 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.user=get_field(&mem, table->field[MYSQL_DB_FIELD_USER]); + const char *hostname= get_field(&mem, table->field[MYSQL_DB_FIELD_HOST]); + if (!hostname && find_acl_role(db.user)) + hostname= ""; + update_hostname(&db.host, hostname); db.db=get_field(&mem, 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) { /* @@ -1073,9 +1362,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); @@ -1093,7 +1380,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) end_read_record(&read_record_info); freeze_size(&acl_dbs); - (void) my_init_dynamic_array(&acl_proxy_users, sizeof(ACL_PROXY_USER), + (void) my_init_dynamic_array(&acl_proxy_users, sizeof(ACL_PROXY_USER), 50, 100, MYF(0)); if (tables[3].table) { @@ -1125,6 +1412,48 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) } freeze_size(&acl_proxy_users); + if (tables[4].table) + { + if (init_read_record(&read_record_info, thd, table= tables[4].table, + NULL, 1, 1, FALSE)) + goto end; + table->use_all_columns(); + /* account for every role mapping */ + + (void) my_hash_init2(&acl_roles_mappings, 50, system_charset_info, + 0,0,0, (my_hash_get_key) acl_role_map_get_key, 0,0); + 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 (&mem) ROLE_GRANT_PAIR; + + if (mapping->init(&mem, 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; @@ -1138,13 +1467,15 @@ end: void acl_free(bool end) { + my_hash_free(&acl_roles); free_root(&mem,MYF(0)); delete_dynamic(&acl_hosts); - delete_dynamic(&acl_users); + delete_dynamic_with_callback(&acl_users, (FREE_FUNC) free_acl_user); delete_dynamic(&acl_dbs); delete_dynamic(&acl_wild_hosts); delete_dynamic(&acl_proxy_users); my_hash_free(&acl_check_hosts); + my_hash_free(&acl_roles_mappings); plugin_unlock(0, native_password_plugin); plugin_unlock(0, old_password_plugin); if (!end) @@ -1178,10 +1509,10 @@ void acl_free(bool end) my_bool acl_reload(THD *thd) { - TABLE_LIST tables[4]; + TABLE_LIST tables[5]; DYNAMIC_ARRAY old_acl_hosts, old_acl_users, old_acl_dbs, old_acl_proxy_users; + HASH old_acl_roles, old_acl_roles_mappings; MEM_ROOT old_mem; - bool old_initialized; my_bool return_val= TRUE; DBUG_ENTER("acl_reload"); @@ -1198,13 +1529,18 @@ my_bool acl_reload(THD *thd) tables[3].init_one_table(C_STRING_WITH_LEN("mysql"), C_STRING_WITH_LEN("proxies_priv"), "proxies_priv", TL_READ); + tables[4].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("roles_mapping"), + "roles_mapping", 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[3].next_local= tables[3].next_global= tables + 4; tables[0].open_type= tables[1].open_type= tables[2].open_type= - tables[3].open_type= OT_BASE_ONLY; - tables[0].open_strategy= tables[3].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - + tables[3].open_type= tables[4].open_type= OT_BASE_ONLY; + tables[0].open_strategy= tables[3].open_strategy= + tables[4].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { /* @@ -1217,11 +1553,13 @@ my_bool acl_reload(THD *thd) 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; old_mem= mem; @@ -1234,6 +1572,8 @@ my_bool acl_reload(THD *thd) 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; @@ -1241,20 +1581,20 @@ my_bool acl_reload(THD *thd) } 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); } - /* Get all access bits from table after fieldnr @@ -1286,8 +1626,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) @@ -1295,6 +1634,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: @@ -1374,12 +1741,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) { @@ -1394,63 +1760,168 @@ 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= dynamic_element(&acl_dbs, i, ACL_DB*); + 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= dynamic_element(&acl_dbs, i, ACL_DB*); + 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; +int acl_check_setrole(THD *thd, 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_exact(thd->security_ctx->priv_host, + thd->security_ctx->priv_user); + 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; + /* Yes! priv_user@host. Don't ask why - that's what check_access() does. */ + if (acl_user->wild_eq(thd->security_ctx->priv_user, + thd->security_ctx->host)) + { + 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_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, my_bool not_used __attribute__((unused))) { @@ -1459,6 +1930,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, @@ -1475,57 +1954,66 @@ 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(&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); + } + 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(&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); } - 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 (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; } } } +static void acl_insert_role(const char *rolename, ulong privileges) +{ + ACL_ROLE *entry; + + mysql_mutex_assert_owner(&acl_cache->lock); + entry= new (&mem) ACL_ROLE(rolename, privileges, &mem); + (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, @@ -1541,8 +2029,9 @@ 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); + acl_user.user.str=*user ? strdup_root(&mem,user) : 0; + acl_user.user.length= strlen(user); + update_hostname(&acl_user.host, safe_strdup_root(&mem, host)); if (plugin->str[0]) { acl_user.plugin= *plugin; @@ -1560,15 +2049,18 @@ static void acl_insert_user(const char *user, const char *host, 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; + (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 || @@ -1579,11 +2071,17 @@ 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) + ulong privileges) { mysql_mutex_assert_owner(&acl_cache->lock); @@ -1603,7 +2101,10 @@ static void acl_update_db(const char *user, const char *host, const char *db, { if (privileges) - acl_db->access=privileges; + { + acl_db->access= privileges; + acl_db->initial_access= acl_db->access; + } else delete_dynamic_element(&acl_dbs,i); } @@ -1628,14 +2129,14 @@ 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); + update_hostname(&acl_db.host, safe_strdup_root(&mem, host)); acl_db.db=strdup_root(&mem,db); - acl_db.access=privileges; + 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, @@ -1643,7 +2144,6 @@ static void acl_insert_db(const char *user, const char *host, const char *db, } - /* Get privilege for a host, user and db combination @@ -1661,7 +2161,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 @@ -1694,12 +2194,15 @@ ulong acl_get(const char *host, const char *ip, { 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 */ } } } @@ -1808,9 +2311,143 @@ void rebuild_check_host(void) 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; + my_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) @@ -1870,28 +2507,28 @@ int check_change_password(THD *thd, const char *host, const char *user, my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); return(1); } + if (!thd->slave_thread && !thd->security_ctx->priv_user[0]) + { + my_message(ER_PASSWORD_ANONYMOUS_USER, ER(ER_PASSWORD_ANONYMOUS_USER), + MYF(0)); + return(1); + } + if (!host) // Role + { + my_error(ER_PASSWORD_NO_MATCH, MYF(0)); + return 1; + } if (!thd->slave_thread && #ifdef WITH_WSREP (!WSREP(thd) || !thd->wsrep_applier) && #endif /* WITH_WSREP */ - (strcmp(thd->security_ctx->user, user) || + (strcmp(thd->security_ctx->priv_user, user) || my_strcasecmp(system_charset_info, host, thd->security_ctx->priv_host))) { if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 0)) return(1); } -#ifdef WITH_WSREP - if ((!WSREP(thd) || !thd->wsrep_applier) && - !thd->slave_thread && !thd->security_ctx->user[0]) -#else - if (!thd->slave_thread && !thd->security_ctx->user[0]) -#endif /* WITH_WSREP */ - { - my_message(ER_PASSWORD_ANONYMOUS_USER, ER(ER_PASSWORD_ANONYMOUS_USER), - MYF(0)); - return(1); - } size_t len= strlen(new_password); if (len && len != SCRAMBLED_PASSWORD_CHAR_LENGTH && len != SCRAMBLED_PASSWORD_CHAR_LENGTH_323) @@ -1986,7 +2623,7 @@ bool change_password(THD *thd, const char *host, const char *user, 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(host, user))) { mysql_mutex_unlock(&acl_cache->lock); my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); @@ -1994,7 +2631,7 @@ bool change_password(THD *thd, const char *host, const char *user, } /* 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); @@ -2007,8 +2644,8 @@ bool change_password(THD *thd, const char *host, const char *user, ER_SET_PASSWORD_AUTH_PLUGIN, ER(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 : "", + safe_str(acl_user->host.hostname), + safe_str(acl_user->user.str), new_password, new_password_len)) { mysql_mutex_unlock(&acl_cache->lock); /* purecov: deadcode */ @@ -2022,8 +2659,8 @@ bool change_password(THD *thd, const char *host, const char *user, { query_length= sprintf(buff,"SET PASSWORD FOR '%-.120s'@'%-.120s'='%-.120s'", - acl_user->user ? acl_user->user : "", - acl_user->host.hostname ? acl_user->host.hostname : "", + safe_str(acl_user->user.str), + safe_str(acl_user->host.hostname), new_password); thd->clear_error(); result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length, @@ -2061,7 +2698,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) @@ -2073,45 +2710,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, + role ? strlen(role) : 0); + 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); } @@ -2148,10 +2836,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 } @@ -2323,6 +3012,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)) @@ -2339,12 +3030,13 @@ 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"); @@ -2362,6 +3054,15 @@ static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, else combo.password= 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, system_charset_info); @@ -2520,6 +3221,16 @@ static int replace_user_table(THD *thd, TABLE *table, LEX_USER &combo, table->field[next_field + 1]->reset(); } } + + /* 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); + } } if (old_row_exists) @@ -2533,10 +3244,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; @@ -2558,27 +3269,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.password.str, combo.password.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.password.str, combo.password.length, + lex->ssl_type, + lex->ssl_cipher, + lex->x509_issuer, + lex->x509_subject, + &lex->mqh, + rights, + &combo.plugin, + &combo.auth); + } } DBUG_RETURN(error); } @@ -2607,10 +3328,14 @@ static int replace_db_table(TABLE *table, const char *db, } /* 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(ER_PASSWORD_NO_MATCH), MYF(0)); + DBUG_RETURN(-1); + } } table->use_all_columns(); @@ -2692,8 +3417,107 @@ abort: DBUG_RETURN(-1); } +/** + Updates the mysql.roles_mapping table and the acl_roles_mappings hash. + + @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; + } + /* + This should always return something, as the check was performed + earlier + */ + my_hash_delete(&acl_roles_mappings, (uchar*)existing); + } + else + { + if (revoke_grant) + existing->with_admin= false; + else + existing->with_admin|= with_admin; + + table->field[3]->store(existing->with_admin + 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; + } + else + { + /* allocate a new entry that will go in the hash */ + ROLE_GRANT_PAIR *hash_entry= new (&mem) ROLE_GRANT_PAIR; + if (hash_entry->init(&mem, user->str, host->str, role->str, with_admin)) + DBUG_RETURN(1); + my_hash_insert(&acl_roles_mappings, (uchar*) hash_entry); + } + + /* all ok */ + DBUG_RETURN(0); + +table_error: + DBUG_PRINT("info", ("table error")); + table->file->print_error(error, MYF(0)); + DBUG_RETURN(1); +} -static void +static void acl_update_proxy_user(ACL_PROXY_USER *new_value, bool is_revoke) { mysql_mutex_assert_owner(&acl_cache->lock); @@ -2701,7 +3525,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)) @@ -2723,7 +3547,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"); @@ -2736,9 +3560,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; @@ -2756,14 +3580,14 @@ replace_proxies_priv_table(THD *thd, TABLE *table, const LEX_USER *user, } /* 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)); 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, @@ -2862,11 +3686,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()); } + + /* this constructor assumes thas source->column is allocated in memex */ + GRANT_COLUMN(GRANT_COLUMN *source) : column(source->column), + rights (source->rights), init_rights(0), key_length(source->key_length) { } }; @@ -2877,13 +3706,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, @@ -2901,6 +3730,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, @@ -2908,6 +3738,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); + } }; @@ -2938,7 +3773,7 @@ void GRANT_NAME::set_user_details(const char *h, const char *d, 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); } @@ -2947,18 +3782,25 @@ 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])); + user= safe_str(get_field(&memex,form->field[2])); + + const char *hostname= get_field(&memex, 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(&memex,form->field[1]); - user= get_field(&memex,form->field[2]); - if (!user) - user= (char*) ""; sort= get_sort(3, host.hostname, db, user); tname= get_field(&memex,form->field[3]); if (!db || !tname) @@ -2980,6 +3822,7 @@ GRANT_NAME::GRANT_NAME(TABLE *form, bool is_routine) 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; } @@ -2996,10 +3839,17 @@ 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; @@ -3022,6 +3872,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; } @@ -3029,7 +3880,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; } @@ -3044,13 +3896,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]) && @@ -3074,9 +3926,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(); } @@ -3131,7 +3983,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) { @@ -3141,7 +3993,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) { @@ -3150,7 +4002,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, @@ -3332,7 +4184,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 { @@ -3389,11 +4244,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(ER_PASSWORD_NO_MATCH), + MYF(0)); /* purecov: deadcode */ + DBUG_RETURN(-1); /* purecov: deadcode */ + } } table->use_all_columns(); @@ -3477,6 +4335,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; } @@ -3506,6 +4367,7 @@ 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) @@ -3514,6 +4376,12 @@ static int replace_routine_table(THD *thd, GRANT_NAME *grant_name, DBUG_RETURN(-1); } + if (revoke_grant && !grant_name->init_privs) // only inherited role privs + { + my_hash_delete(hash, (uchar*) grant_name); + DBUG_RETURN(0); + } + get_grantor(thd, grantor); /* New users are created before this function is called. @@ -3543,6 +4411,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 @@ -3597,12 +4468,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); @@ -3613,6 +4484,886 @@ 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, const char *name) +{ + + 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 + */ + my_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(ACL_DB * const *db1, ACL_DB * const *db2) +{ + return strcmp((*db1)->db, (*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(ACL_DB *merged, ACL_DB **first, ulong access, char *role) +{ + if (!first) + return 0; + + DBUG_EXECUTE_IF("role_merge_stats", role_db_merges++;); + + if (merged == NULL) + { + /* + 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= first[0]->db; + acl_db.access= access; + acl_db.initial_access= 0; + acl_db.sort=get_sort(3, "", acl_db.db, role); + push_dynamic(&acl_dbs,(uchar*) &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. + */ + merged->sort= 0; // lower than any valid ACL_DB sort value, will be sorted last + return 4; + } + else if (merged->access != access) + { + /* this is easy */ + 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<ACL_DB *> 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= dynamic_element(&acl_dbs,i,ACL_DB*); + 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(db); + } + 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. + */ + ACL_DB **first= NULL, *UNINIT_VAR(merged); + ulong UNINIT_VAR(access), update_flags= 0; + for (ACL_DB **cur= dbs.front(); cur <= dbs.back(); cur++) + { + if (!first || (!dbname && strcmp(cur[0]->db, cur[-1]->db))) + { // new db name series + update_flags|= update_role_db(merged, first, access, grantee->user.str); + merged= NULL; + access= 0; + first= cur; + } + if (strcmp(cur[0]->user, grantee->user.str) == 0) + access|= (merged= cur[0])->initial_access; + else + access|= cur[0]->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 + my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, + sizeof(ACL_DB),(qsort_cmp) acl_compare); + } + if (update_flags & 4) + { // deleted, trim the end + while (acl_dbs.elements && + dynamic_element(&acl_dbs, acl_dbs.elements-1, ACL_DB*)->sort == 0) + acl_dbs.elements--; + } + 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 (&memex) 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 (&memex) 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+= 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(test(db) == 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; + + 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 (&memex) 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(test(db) == test(tname)); // both must be set, or neither + + DBUG_EXECUTE_IF("role_merge_stats", role_routine_merges++;); + + 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; + + if (--grantee->counter) + return 1; // don't recurse into grantee just yet + + /* 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 +} + +/***************************************************************** + End of the role privilege propagation and graph traversal code +******************************************************************/ + +bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, LEX *lex) +{ + if (to != from) + { + /* preserve authentication information, if LEX_USER was reallocated */ + to->password= from->password; + to->plugin= from->plugin; + to->auth= from->auth; + } + + /* + Note, that no password is null_lex_str, while no plugin is empty_lex_str. + See sql_yacc.yy + */ + bool has_auth= to->password.str || to->plugin.length || to->auth.length || + lex->ssl_type != SSL_TYPE_NOT_SPECIFIED || lex->ssl_cipher || + lex->x509_issuer || lex->x509_subject || + lex->mqh.specified_limits; + + /* + Specifying authentication clauses forces the name to be interpreted + as a user, not a role. See also check_change_password() + */ + if (to->is_role() && has_auth) + { + my_error(ER_PASSWORD_NO_MATCH, MYF(0)); + return true; + } + + return false; +} + + /* Store table level and column level grants in the privilege tables @@ -3748,7 +5499,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, } #endif - /* + /* The lock api is depending on the thd->lex variable which needs to be re-initialized. */ @@ -3779,16 +5530,19 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_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)); + if (copy_and_check_auth(Str, tmp_Str, thd->lex)) + error= -1; + else + 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) { result= TRUE; // Remember error @@ -3865,15 +5619,17 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, } else if (tables[2].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[2].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); @@ -3985,11 +5741,11 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, { 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, @@ -4005,7 +5761,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, 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,12 +5783,16 @@ 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, + 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); @@ -4049,6 +5809,277 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, DBUG_RETURN(result); } +static void append_user(String *str, const char *u, const char *h) +{ + if (str->length()) + str->append(','); + str->append('\''); + str->append(u); + /* hostname part is not relevant for roles, it is always empty */ + if (*h) + { + str->append(STRING_WITH_LEN("'@'")); + str->append(h); + } + str->append('\''); +} + +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); + bool result= 0; + 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; + + TABLE_LIST tables; + tables.init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("roles_mapping"), + "roles_mapping", TL_WRITE); + + if (open_and_lock_tables(thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) + DBUG_RETURN(TRUE); /* purecov: deadcode */ + + 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_user(&wrong_users, "NONE", ""); + result= 1; + continue; + } + if (!(role_as_user= find_acl_role(thd->security_ctx->priv_role))) + { + append_user(&wrong_users, thd->security_ctx->priv_role, ""); + result= 1; + continue; + } + + /* can not grant current_role to current_role */ + if (granted_role->user.str == current_role.str) + { + append_user(&wrong_users, thd->security_ctx->priv_role, ""); + 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(&wrong_users, username.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 (!grantee) + grantee= find_user_exact(hostname.str, username.str); + + if (!grantee) + { + append_user(&wrong_users, username.str, hostname.str); + 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(&wrong_users, username.str, ""); + result= 1; + undo_add_role_user_mapping(grantee, role); + continue; + } + } + } + else + { + /* grant was already removed or never existed */ + if (!hash_entry) + { + append_user(&wrong_users, username.str, hostname.str); + 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.table, + &username, &hostname, &rolename, + thd->lex->with_admin_option, + hash_entry, revoke)) + { + append_user(&wrong_users, username.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; + } + + /* + Only need to propagate grants when granting/revoking a role to/from + a role + */ + if (role_as_user) + propagate_role_grants(role_as_user, PRIVS_TO_MERGE::ALL, 0, 0); + } + + 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) @@ -4093,12 +6124,12 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), C_STRING_WITH_LEN("proxies_priv"), - "proxies_priv", + "proxies_priv", TL_WRITE); else tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("db"), - "db", + C_STRING_WITH_LEN("db"), + "db", TL_WRITE); tables[0].next_local= tables[0].next_global= tables+1; @@ -4132,23 +6163,25 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, mysql_mutex_lock(&acl_cache->lock); grant_version++; + 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 + } + int result=0; while ((tmp_Str = str_list++)) { - if (!(Str= get_current_user(thd, tmp_Str))) + if (!(Str= get_current_user(thd, tmp_Str, false))) { 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 (copy_and_check_auth(Str, tmp_Str, thd->lex)) + result= -1; + else if (replace_user_table(thd, tables[0].table, *Str, (!db ? rights : 0), revoke_grant, create_new_users, test(thd->variables.sql_mode & @@ -4172,10 +6205,14 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, else if (is_proxy) { if (replace_proxies_priv_table (thd, tables[1].table, Str, proxied_user, - rights & GRANT_ACL ? TRUE : FALSE, + rights & GRANT_ACL ? TRUE : FALSE, revoke_grant)) result= -1; } + if (Str->is_role()) + propagate_role_grants(find_acl_role(Str->user.str), + db ? PRIVS_TO_MERGE::DB : PRIVS_TO_MERGE::GLOBAL, + db, 0); } mysql_mutex_unlock(&acl_cache->lock); @@ -4234,105 +6271,6 @@ my_bool grant_init() /** - @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); -} - - -/** @brief Initialize structures responsible for table/column-level privilege checking and load information about grants from open privilege tables. @@ -4351,7 +6289,7 @@ static my_bool grant_load(THD *thd, TABLE_LIST *tables) { MEM_ROOT *memex_ptr; my_bool return_val= 1; - TABLE *t_table= 0, *c_table= 0; + 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); @@ -4363,9 +6301,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(&memex, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); - t_table = tables[0].table; - c_table = tables[1].table; + t_table= tables[0].table; + c_table= tables[1].table; + p_table= tables[2].table; // this can be NULL if (t_table->file->ha_index_init(0, 1)) goto end_index_init; @@ -4373,10 +6317,11 @@ static my_bool grant_load(THD *thd, TABLE_LIST *tables) t_table->use_all_columns(); c_table->use_all_columns(); + memex_ptr= &memex; + 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; @@ -4393,9 +6338,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; } } @@ -4411,8 +6355,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); @@ -4422,56 +6430,16 @@ end_index_init: } -/** - @brief Helper function to grant_reload. Reloads procs_priv table is it - exists. - - @param thd A pointer to the thread handler object. - - @see grant_reload - - @return Error state - @retval FALSE Success - @retval TRUE An error has occurred. -*/ - -static my_bool grant_reload_procs_priv(THD *thd) +my_bool role_propagate_grants_action(void *ptr, void *unused __attribute__((unused))) { - HASH old_proc_priv_hash, old_func_priv_hash; - TABLE_LIST table; - my_bool return_val= FALSE; - DBUG_ENTER("grant_reload_procs_priv"); - - table.init_one_table("mysql", 5, "procs_priv", - strlen("procs_priv"), "procs_priv", - TL_READ); - table.open_type= OT_BASE_ONLY; - - if (open_and_lock_tables(thd, &table, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) - DBUG_RETURN(TRUE); - - mysql_rwlock_wrlock(&LOCK_grant); - /* 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")); - grant_free(); - 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); - } - mysql_rwlock_unlock(&LOCK_grant); + ACL_ROLE *role= (ACL_ROLE *)ptr; + if (role->counter) + return 0; - close_mysql_tables(thd); - 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; } @@ -4492,8 +6460,8 @@ static my_bool grant_reload_procs_priv(THD *thd) my_bool grant_reload(THD *thd) { - TABLE_LIST tables[2]; - HASH old_column_priv_hash; + TABLE_LIST tables[3]; + HASH old_column_priv_hash, old_proc_priv_hash, old_func_priv_hash; MEM_ROOT old_mem; my_bool return_val= 1; DBUG_ENTER("grant_reload"); @@ -4508,8 +6476,13 @@ my_bool grant_reload(THD *thd) 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[0].open_type= tables[1].open_type= OT_BASE_ONLY; + 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; + tables[2].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; /* To avoid deadlocks we should obtain table locks before @@ -4519,41 +6492,42 @@ my_bool grant_reload(THD *thd) goto end; 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, MYF(0)); if ((return_val= grant_load(thd, tables))) { // Error. Revert to old hash DBUG_PRINT("error",("Reverting to old privileges")); grant_free(); /* purecov: deadcode */ column_priv_hash= old_column_priv_hash; /* purecov: deadcode */ + proc_priv_hash= old_proc_priv_hash; + func_priv_hash= old_func_priv_hash; memex= 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)); } - mysql_rwlock_unlock(&LOCK_grant); - close_mysql_tables(thd); - /* - It is OK failing to load procs_priv table because we may be - working with 4.1 privilege tables. - */ - if (grant_reload_procs_priv(thd)) - return_val= 1; + mysql_mutex_lock(&acl_cache->lock); + my_hash_iterate(&acl_roles, role_propagate_grants_action, NULL); + mysql_mutex_unlock(&acl_cache->lock); - mysql_rwlock_wrlock(&LOCK_grant); - grant_version++; mysql_rwlock_unlock(&LOCK_grant); + close_mysql_tables(thd); + end: DBUG_RETURN(return_val); } @@ -4611,6 +6585,8 @@ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, uint i; ulong orig_want_access= want_access; my_bool locked= 0; + GRANT_TABLE *grant_table; + GRANT_TABLE *grant_table_role= NULL; DBUG_ENTER("check_grant"); DBUG_ASSERT(number > 0); @@ -4709,16 +6685,21 @@ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, mysql_rwlock_rdlock(&LOCK_grant); } - GRANT_TABLE *grant_table= table_hash_search(sctx->host, sctx->ip, - tl->get_db_name(), - sctx->priv_user, - tl->get_table_name(), - FALSE); + grant_table= table_hash_search(sctx->host, sctx->ip, + tl->get_db_name(), + sctx->priv_user, + tl->get_table_name(), + FALSE); + if (sctx->priv_role[0]) + grant_table_role= table_hash_search("", NULL, tl->get_db_name(), + sctx->priv_role, + tl->get_table_name(), + TRUE); - if (!grant_table) + if (!grant_table && !grant_table_role) { - want_access &= ~tl->grant.privilege; - goto err; // No grants + want_access&= ~tl->grant.privilege; + goto err; } /* @@ -4728,18 +6709,21 @@ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, if (any_combination_will_do) continue; - tl->grant.grant_table= grant_table; // Remember for column test + tl->grant.grant_table_user= grant_table; // Remember for column test + tl->grant.grant_table_role= grant_table_role; tl->grant.version= grant_version; - tl->grant.privilege|= grant_table->privs; + tl->grant.privilege|= grant_table ? grant_table->privs : 0; + tl->grant.privilege|= grant_table_role ? grant_table_role->privs : 0; tl->grant.want_privilege= ((want_access & COL_ACLS) & ~tl->grant.privilege); if (!(~tl->grant.privilege & want_access)) continue; - if (want_access & ~(grant_table->cols | tl->grant.privilege)) + if ((want_access&= ~((grant_table ? grant_table->cols : 0) | + (grant_table_role ? grant_table_role->cols : 0) | + tl->grant.privilege))) { - want_access &= ~(grant_table->cols | tl->grant.privilege); - goto err; // impossible + goto err; // impossible } } if (locked) @@ -4788,6 +6772,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"); @@ -4802,17 +6787,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); @@ -4822,6 +6830,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, @@ -4870,7 +6879,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); @@ -4902,7 +6911,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 @@ -4911,24 +6920,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. */ @@ -4952,26 +6960,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; } } @@ -4990,7 +7018,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, @@ -5016,6 +7044,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; @@ -5028,11 +7062,12 @@ 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]; + uint len, UNINIT_VAR(len2); bool error= TRUE; end= strmov(helping, sctx->priv_user) + 1; @@ -5043,6 +7078,18 @@ bool check_grant_db(THD *thd,const char *db) len= (uint) (end - helping) + 1; + /* + If a role is set, we need to check for privileges + here aswell + */ + 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++) @@ -5057,6 +7104,14 @@ bool check_grant_db(THD *thd,const char *db) error= FALSE; /* Found match. */ break; } + if (sctx->priv_role[0] && + len2 < grant_table->key_length && + !memcmp(grant_table->hash_key,helping2,len) && + (!grant_table->host.hostname || !grant_table->host.hostname[0])) + { + error= FALSE; /* Found role match */ + break; + } } if (error) @@ -5093,6 +7148,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; @@ -5106,6 +7162,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) { @@ -5137,9 +7199,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 @@ -5147,11 +7209,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; @@ -5163,6 +7225,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; } @@ -5178,18 +7249,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; @@ -5219,31 +7298,52 @@ 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->privs | grant_column->rights); + } } mysql_rwlock_unlock(&LOCK_grant); return priv; @@ -5283,9 +7383,43 @@ 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; +} /* @@ -5295,18 +7429,18 @@ 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; + char *username= NULL; + char *hostname= NULL; + 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"); @@ -5316,26 +7450,54 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) 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 (lex_user->user.str == current_user.str) { - mysql_mutex_unlock(&acl_cache->lock); - mysql_rwlock_unlock(&LOCK_grant); + username= thd->security_ctx->priv_user; + hostname= thd->security_ctx->priv_host; + } + else if (lex_user->user.str == current_role.str) + { + rolename= thd->security_ctx->priv_role; + } + else 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; + } + else + { + lex_user= get_current_user(thd, lex_user, false); + if (!lex_user) + { + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + DBUG_RETURN(TRUE); + } - my_error(ER_NONEXISTING_GRANT, MYF(0), - lex_user->user.str, lex_user->host.str); - DBUG_RETURN(TRUE); + if (lex_user->is_role()) + { + rolename= lex_user->user.str; + } + else + { + username= lex_user->user.str; + hostname= lex_user->host.str; + } } + DBUG_ASSERT(rolename || username); 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); + if (!username) + strxmov(buff,"Grants for ",rolename, NullS); + else + strxmov(buff,"Grants for ",username,"@",hostname, NullS); field_list.push_back(field); if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) { mysql_mutex_unlock(&acl_cache->lock); mysql_rwlock_unlock(&LOCK_grant); @@ -5343,39 +7505,188 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) DBUG_RETURN(TRUE); } - /* Add first global access grants */ + if (username) { - String global(buff,sizeof(buff),system_charset_info); - global.length(0); - global.append(STRING_WITH_LEN("GRANT ")); + acl_user= find_user_exact(hostname, username); + if (!acl_user) + { + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); - 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")); + 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; + } + + if (rolename) + { + 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 { - bool found=0; - ulong j,test_access= want_access & ~GRANT_ACL; - for (counter=0, j = SELECT_ACL;j <= GLOBAL_ACLS;counter++,j <<= 1) + if (lex_user->user.str == current_role.str) { - if (test_access & j) - { - if (found) - global.append(STRING_WITH_LEN(", ")); - found=1; - global.append(command_array[counter],command_lengths[counter]); - } + 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); } } - 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); + } + + 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++) + { + 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('\''); + + 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()) + { + 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 (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(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) { @@ -5389,14 +7700,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) @@ -5409,67 +7720,75 @@ 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)) { 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", 0); add_user_option(&global, acl_user->user_resource.updates, - "MAX_UPDATES_PER_HOUR", 0); + "MAX_UPDATES_PER_HOUR", 0); add_user_option(&global, acl_user->user_resource.conn_per_hour, - "MAX_CONNECTIONS_PER_HOUR", 0); + "MAX_CONNECTIONS_PER_HOUR", 0); 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", 1); } } - /* Add database access */ + 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) +{ + ACL_DB *acl_db; + ulong want_access; + uint counter; + Protocol *protocol= thd->protocol; + for (counter=0 ; counter < acl_dbs.elements ; counter++) { 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= ""; + user= safe_str(acl_db->user); + host=acl_db->host.hostname; /* We do not make SHOW GRANTS case-sensitive here (like REVOKE), @@ -5478,68 +7797,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), @@ -5548,132 +7883,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 + { + table_access= grant_table->privs; + cols_access= grant_table->cols; + } + else // Role { - String global(buff, sizeof(buff), system_charset_info); - ulong test_access= (table_access | grant_table->cols) & ~GRANT_ACL; + 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; @@ -5685,10 +8014,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), @@ -5697,10 +8024,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); @@ -5737,11 +8069,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")); @@ -5758,6 +8093,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. */ @@ -5794,7 +8130,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)); @@ -5808,7 +8144,7 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) SYNOPSIS open_grant_tables() thd The current thread. - tables (out) The 4 elements array for the opened tables. + tables (out) The 7 elements array for the opened tables. DESCRIPTION Tables are numbered as follows: @@ -5816,6 +8152,9 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) 1 db 2 tables_priv 3 columns_priv + 4 procs_priv + 5 proxies_priv + 6 roles_mapping RETURN 1 Skip GRANT handling during replication. @@ -5823,8 +8162,8 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) < 0 Error. */ -#define GRANT_TABLES 6 -int open_grant_tables(THD *thd, TABLE_LIST *tables) +#define GRANT_TABLES 7 +static int open_grant_tables(THD *thd, TABLE_LIST *tables) { Rpl_filter *rpl_filter= thd->rpl_filter; DBUG_ENTER("open_grant_tables"); @@ -5851,13 +8190,19 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) (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+5)->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + (tables+6)->init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("roles_mapping"), + "roles_mapping", TL_WRITE); + (tables+6)->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; + (tables+5)->next_local= (tables+5)->next_global= tables + 6; #ifdef HAVE_REPLICATION /* @@ -5871,11 +8216,13 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) account in tests. */ tables[0].updating= tables[1].updating= tables[2].updating= - tables[3].updating= tables[4].updating= tables[5].updating= 1; + tables[3].updating= tables[4].updating= tables[5].updating= + tables[6].updating= 1; if (!(thd->spcont || 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; + tables[3].updating= tables[4].updating= tables[5].updating= + tables[6].updating= 0; } #endif @@ -5887,8 +8234,7 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) DBUG_RETURN(0); } -ACL_USER *check_acl_user(LEX_USER *user_name, - uint *acl_acl_userdx) +ACL_USER *check_acl_user(LEX_USER *user_name, uint *acl_acl_userdx) { ACL_USER *acl_user= 0; uint counter; @@ -5897,14 +8243,8 @@ ACL_USER *check_acl_user(LEX_USER *user_name, 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)) + if(acl_user->eq(user_name->user.str, user_name->host.str)) break; } if (counter == acl_users.elements) @@ -5948,7 +8288,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)); @@ -5966,6 +8306,89 @@ static int modify_grant_table(TABLE *table, Field *host_field, } /* + Handle the roles_mappings 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= current_thd; + 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_mappings 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 (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 @@ -5990,6 +8413,8 @@ static int modify_grant_table(TABLE *table, Field *host_field, 2 tables_priv 3 columns_priv 4 procs_priv + 5 proxies_priv + 6 roles_mapping RETURN > 0 At least one record matched. @@ -6005,8 +8430,8 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, 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; + 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]; @@ -6014,6 +8439,12 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, DBUG_ENTER("handle_grant_table"); THD *thd= current_thd; + if (table_no == 6) + { + result= handle_roles_mappings_table(table, drop, user_from, user_to); + DBUG_RETURN(result); + } + table->use_all_columns(); if (! table_no) // mysql.user table { @@ -6035,9 +8466,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) { @@ -6072,7 +8509,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) @@ -6081,10 +8518,8 @@ 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) @@ -6121,7 +8556,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. @@ -6132,13 +8567,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. @@ -6148,24 +8576,78 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, LEX_USER *user_from, LEX_USER *user_to) { int result= 0; - uint idx; - uint elements; - const char *user; - const char *host; + int idx; + int elements; + const char *UNINIT_VAR(user); + const char *UNINIT_VAR(host); + const char *UNINIT_VAR(role); + uint role_not_matched= 1; 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, 0, 0); + + // 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(&mem, 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: @@ -6189,17 +8671,21 @@ 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 DBUG_PRINT("loop",("scan struct: %u search user: '%s' host: '%s'", struct_no, user_from->user.str, user_from->host.str)); #endif - /* Loop over all elements. */ - for (idx= 0; idx < elements; idx++) + /* Loop over all elements *backwards* (see the comment below). */ + for (idx= elements - 1; idx >= 0; idx--) { /* Get a pointer to the element. @@ -6207,7 +8693,7 @@ 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; @@ -6231,6 +8717,13 @@ 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; + role= role_grant_pair->r_uname; + break; + default: DBUG_ASSERT(0); } @@ -6238,20 +8731,36 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, user= ""; if (! host) host= ""; + if (! role) + role= ""; #ifdef EXTRA_DEBUG 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)) + + if (struct_no == ROLES_MAPPINGS_HASH) + { + role_not_matched= strcmp(user_from->user.str, role); + if (role_not_matched && + (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 ) { + elements--; switch ( struct_no ) { case USER_ACL: + free_acl_user(dynamic_element(&acl_users, idx, ACL_USER*)); delete_dynamic_element(&acl_users, idx); break; @@ -6263,34 +8772,36 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, case PROC_PRIVILEGES_HASH: case FUNC_PRIVILEGES_HASH: my_hash_delete(grant_name_hash, (uchar*) grant_name); + /* + 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. + */ + if (idx != elements) + idx++; break; case PROXY_USERS_ACL: delete_dynamic_element(&acl_proxy_users, idx); break; + case ROLES_MAPPINGS_HASH: + my_hash_delete(roles_mappings_hash, (uchar*) role_grant_pair); + break; + + default: + DBUG_ASSERT(0); + break; } - elements--; - /* - - If we are iterating through an array then we just have moved all - elements after the current element one position closer to its head. - This means that we have to take another look at the element at - current position as it is a new element from the array's tail. - - If we are iterating through a hash the current element was replaced - with one of elements from the tail. So we also have to take a look - at the new element in current position. - Note that in our HASH implementation hash_delete() won't move any - elements with position after current one to position before the - current (i.e. from the tail to the head), so it is safe to continue - iteration without re-starting. - */ - idx--; } else if ( user_to ) { switch ( struct_no ) { case USER_ACL: - acl_user->user= strdup_root(&mem, user_to->user.str); + acl_user->user.str= strdup_root(&mem, user_to->user.str); + acl_user->user.length= user_to->user.length; acl_user->host.hostname= strdup_root(&mem, user_to->host.str); break; @@ -6304,7 +8815,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; @@ -6325,15 +8836,15 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, my_hash_update(grant_name_hash, (uchar*) grant_name, (uchar*) old_key, old_key_length); /* - hash_update() operation could have moved element from the tail - of the hash to the current position. So we need to take a look - at the element in current position once again. - Thanks to the fact that hash_update() for our HASH implementation - won't move any elements from the tail of the hash to the positions - before the current one (a.k.a. head) it is safe to continue - iteration without restarting. + hash_update() operation could have moved element from the tail or + the head of the hash to the current position. But it can never + move an element from the head to the tail or from the tail to the + head over the current element. + So we need to examine the current element once again, but + we don't need to restart the search from the beginning. */ - idx--; + if (idx != elements) + idx++; break; } @@ -6341,7 +8852,38 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, acl_proxy_user->set_user (&mem, user_to->user.str); acl_proxy_user->set_host (&mem, 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 (role_not_matched) + oom= role_grant_pair->init(&mem, user_to->user.str, + user_to->host.str, + role_grant_pair->r_uname, false); + else + oom= role_grant_pair->init(&mem, role_grant_pair->u_uname, + role_grant_pair->u_hname, + user_to->user.str, false); + if (oom) + DBUG_RETURN(-1); + + my_hash_update(roles_mappings_hash, (uchar*) role_grant_pair, + (uchar*) old_key, old_key_length); + break; + } + + default: + DBUG_ASSERT(0); + break; } + } else { @@ -6386,24 +8928,21 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, { 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. */ @@ -6415,13 +8954,14 @@ 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); } } @@ -6434,21 +8974,21 @@ 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; } } @@ -6465,7 +9005,7 @@ 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; } @@ -6478,9 +9018,11 @@ 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; } } @@ -6495,27 +9037,74 @@ 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; + } + } + + /* Handle roles_mappings table. */ + if (tables[6].table) + { + if ((found= handle_grant_table(tables, 6, 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; } } - end: + + /* 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 + { + 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); } - 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); + /* hostname part is not relevant for roles, it is always empty */ + if (!user->is_role()) + { + str->append(STRING_WITH_LEN("'@'")); + str->append(user->host.str); + } str->append('\''); } +static void append_str(String *str, const char *s, size_t l) +{ + if (str->length()) + str->append(','); + str->append(s, l); +} /* Create a list of users. @@ -6524,21 +9113,26 @@ 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; DBUG_ENTER("mysql_create_user"); + DBUG_PRINT("entry", ("Handle as %s", handle_as_role ? "role" : "user")); + + 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))) @@ -6547,21 +9141,40 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) 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(&wrong_users, user_name); result= TRUE; continue; } + if (!user_name->host.str) + user_name->host= host_not_specified; + /* 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)) { append_user(&wrong_users, user_name); + result= TRUE; continue; } @@ -6571,13 +9184,43 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) { append_user(&wrong_users, user_name); result= TRUE; + continue; + } + + // every created role is automatically granted to its creator-admin + if (handle_as_role) + { + 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[6].table, + &thd->lex->definer->user, + &thd->lex->definer->host, + &user_name->user, true, + NULL, false)) + { + append_user(&wrong_users, user_name); + if (grantee) + undo_add_role_user_mapping(grantee, role); + result= TRUE; + } } } 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) result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); @@ -6586,7 +9229,6 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) DBUG_RETURN(result); } - /* Drop a list of users and all their privileges. @@ -6600,7 +9242,7 @@ 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; @@ -6610,6 +9252,7 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) bool some_users_deleted= FALSE; ulonglong old_sql_mode= thd->variables.sql_mode; DBUG_ENTER("mysql_drop_user"); + 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))) @@ -6622,27 +9265,50 @@ 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))) + 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_as_role != user_name->is_role()) + { + append_user(&wrong_users, tmp_user_name); + result= TRUE; + continue; + } + if (handle_grant_data(tables, 1, user_name, NULL) <= 0) { append_user(&wrong_users, user_name); result= TRUE; continue; } + some_users_deleted= 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) result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); @@ -6652,7 +9318,6 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) DBUG_RETURN(result); } - /* Rename a user. @@ -6688,18 +9353,21 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) 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)) || + user_from->is_role()) { + append_user(&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)) || + user_to->is_role()) { + append_user(&wrong_users, user_to); result= TRUE; continue; - } - DBUG_ASSERT(user_to != 0); /* Syntax enforces pairs of users. */ + } /* Search all in-memory structures and grant tables @@ -6708,21 +9376,28 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) if (handle_grant_data(tables, 0, user_to, NULL) || handle_grant_data(tables, 0, user_from, user_to) <= 0) { + /* NOTE TODO renaming roles is not yet implemented */ append_user(&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()); @@ -6765,19 +9440,21 @@ 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[0].table, *lex_user, ~(ulong)0, 1, 0, 0)) { result= -1; continue; @@ -6796,12 +9473,11 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) 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= ""; - 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, @@ -6828,10 +9504,8 @@ 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)) @@ -6874,10 +9548,8 @@ 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)) @@ -6896,6 +9568,59 @@ 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[6].table, + &lex_user->user, + &lex_user->host, + &role_grant->user, false, pair, true)) + { + result= -1; //Something went wrong + } + /* + 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, 0, 0); + } } mysql_mutex_unlock(&acl_cache->lock); @@ -7023,11 +9748,8 @@ 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; - + 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[4].table,lex_user, grant_proc->db, grant_proc->tname, is_proc, ~(ulong)0, 1) == 0) @@ -7082,13 +9804,13 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, mysql_mutex_lock(&acl_cache->lock); - if ((au= find_acl_user(combo->host.str=(char*)sctx->host_or_ip,combo->user.str,FALSE))) + if ((au= find_user_wild(combo->host.str=(char*)sctx->host_or_ip, combo->user.str))) goto found_acl; - if ((au= find_acl_user(combo->host.str=(char*)sctx->host, combo->user.str,FALSE))) + if ((au= find_user_wild(combo->host.str=(char*)sctx->host, combo->user.str))) goto found_acl; - if ((au= find_acl_user(combo->host.str=(char*)sctx->ip, combo->user.str,FALSE))) + if ((au= find_user_wild(combo->host.str=(char*)sctx->ip, combo->user.str))) goto found_acl; - if((au= find_acl_user(combo->host.str=(char*)"%", combo->user.str, FALSE))) + if ((au= find_user_wild(combo->host.str=(char*)"%", combo->user.str))) goto found_acl; mysql_mutex_unlock(&acl_cache->lock); @@ -7165,7 +9887,7 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, @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 @@ -7173,7 +9895,7 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, */ 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; @@ -7188,10 +9910,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); @@ -7206,7 +9928,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) { @@ -7237,7 +9959,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); @@ -7246,7 +9968,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, @@ -7267,7 +9989,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; @@ -7276,7 +9999,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); @@ -7293,9 +10016,119 @@ 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_BASE *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); + + if (schema_table_store_record(table->in_use, table)) + return -1; + return 0; +} #endif /*NO_EMBEDDED_ACCESS_CHECKS */ +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) { @@ -7388,16 +10221,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"; @@ -7420,7 +10251,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)) { @@ -7464,10 +10295,8 @@ int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond) 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= ""; + user= safe_str(acl_db->user); + host= safe_str(acl_db->host.hostname); if (no_global_access && (strcmp(thd->security_ctx->priv_user, user) || @@ -7537,11 +10366,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) || @@ -7591,7 +10418,7 @@ int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond) } } } - } + } } err: mysql_rwlock_unlock(&LOCK_grant); @@ -7621,11 +10448,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) || @@ -7703,9 +10528,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) { @@ -7724,22 +10547,40 @@ void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, DBUG_VOID_RETURN; // it is slave } - /* db privileges */ - grant->privilege|= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, 0); + if (!thd->db || strcmp(db, thd->db)) + { + /* 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; + } /* 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); @@ -7761,6 +10602,54 @@ 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 (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; @@ -7925,9 +10814,9 @@ static void login_failed_error(THD *thd) thd->main_security_ctx.host_or_ip, thd->password ? ER(ER_YES) : ER(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) @@ -7935,7 +10824,7 @@ static void login_failed_error(THD *thd) sql_print_warning(ER(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(ER_YES) : ER(ER_NO)); } } @@ -7944,7 +10833,7 @@ static void login_failed_error(THD *thd) after the connection was established Packet format: - + Bytes Content ----- ---- 1 protocol version (always 10) @@ -8038,7 +10927,7 @@ 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; @@ -8068,7 +10957,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) @@ -8093,7 +10982,7 @@ static bool secure_auth(THD *thd) using a different authentication plugin Packet format: - + Bytes Content ----- ---- 1 byte with the value 254 @@ -8159,7 +11048,7 @@ static bool send_plugin_request_packet(MPVIO_EXT *mpvio, 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, @@ -8170,13 +11059,10 @@ 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 */ @@ -8187,16 +11073,11 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) 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(&mem); + mysql_mutex_unlock(&acl_cache->lock); if (!mpvio->acl_user) @@ -8248,8 +11129,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", @@ -8259,6 +11139,56 @@ 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, + const CHARSET_INFO *from_cs) +{ + size_t length, length_length; + size_t max_bytes_available= end - *ptr; + /* not enough bytes to hold the length */ + if ((*ptr) >= (end - 1)) + return true; + + /* read the length */ + if (max_bytes_available >= 9) + { + char *ptr_save= *ptr; + length= net_field_length_ll((uchar **) ptr); + length_length= *ptr - ptr_save; + DBUG_ASSERT(length_length <= 9); + } + else + { + /* to avoid reading unallocated and uninitialized memory */ + char buf[10]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',}, + *len_ptr= buf; + memcpy(buf, *ptr, max_bytes_available); + length= net_field_length_ll((uchar **) &len_ptr); + length_length= len_ptr - buf; + *ptr+= length_length; + if (max_bytes_available < length_length) + return true; + } + max_bytes_available-= length_length; + + /* length says there're more data than can fit into the packet */ + if (length > max_bytes_available) + 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() */ @@ -8311,13 +11241,14 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) 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 */ @@ -8360,13 +11291,13 @@ 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)); DBUG_RETURN(1); } - client_plugin= fix_plugin_ptr(client_plugin); + client_plugin= fix_plugin_ptr(next_field); + next_field+= strlen(next_field) + 1; } else { @@ -8378,7 +11309,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) @@ -8386,10 +11317,18 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) } } + if ((mpvio->thd->client_capabilities & CLIENT_CONNECT_ATTRS) && + read_client_connect_attrs(&next_field, end, + mpvio->thd->charset())) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(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; @@ -8524,7 +11463,8 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, /* strlen() can't be easily deleted without changing protocol */ db_len= db ? strlen(db) : 0; - 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) @@ -8591,6 +11531,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 { @@ -8605,14 +11546,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->thd->charset())) + return packet_error; + /* if the acl_user needs a different plugin to authenticate (specified in GRANT ... AUTHENTICATED VIA plugin_name ..) @@ -8917,7 +11863,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; @@ -9026,7 +11972,7 @@ bool acl_authenticate(THD *thd, uint connect_errors, mpvio.status= MPVIO_EXT::FAILURE; mpvio.make_it_fail= false; 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)); @@ -9054,7 +12000,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); } /* @@ -9074,7 +12020,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 @@ -9089,12 +12035,12 @@ 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) @@ -9130,7 +12076,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, @@ -9153,13 +12099,12 @@ 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) { Host_errors errors; @@ -9176,8 +12121,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; @@ -9210,10 +12155,10 @@ bool acl_authenticate(THD *thd, uint connect_errors, 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)) + 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() } else @@ -9223,7 +12168,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; @@ -9383,7 +12328,7 @@ static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, 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; diff --git a/sql/sql_acl.h b/sql/sql_acl.h index d519279e9c2..df523fae1ca 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 | \ @@ -114,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 @@ -145,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 { @@ -181,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) { @@ -196,7 +201,7 @@ my_bool acl_init(bool dont_read_acl_tables); my_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); + 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); bool acl_getroot(Security_context *sctx, char *user, char *host, char *ip, char *db); @@ -204,39 +209,43 @@ 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); + char *password); + +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); + List <LEX_USER> &user_list, ulong rights, + bool revoke, bool write_to_binlog); my_bool grant_init(); void grant_free(void); my_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); 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 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, @@ -390,4 +399,11 @@ 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); + +#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_array.h b/sql/sql_array.h index 697819787f2..8202e94ce41 100644 --- a/sql/sql_array.h +++ b/sql/sql_array.h @@ -117,6 +117,7 @@ public: */ Elem& at(size_t idx) { + DBUG_ASSERT(idx < array.elements); return *(((Elem*)array.buffer) + idx); } /// Const variant of at(), which cannot change data @@ -125,32 +126,28 @@ public: return *(((Elem*)array.buffer) + idx); } - /// @returns pointer to first element; undefined behaviour if array is empty + /// @returns pointer to first element Elem *front() { - DBUG_ASSERT(array.elements >= 1); return (Elem*)array.buffer; } - /// @returns pointer to first element; undefined behaviour if array is empty + /// @returns pointer to first element const Elem *front() const { - DBUG_ASSERT(array.elements >= 1); return (const Elem*)array.buffer; } - /// @returns pointer to last element; undefined behaviour if array is empty. + /// @returns pointer to last element Elem *back() { - DBUG_ASSERT(array.elements >= 1); - return ((Elem*)array.buffer) + (array.elements - 1); + return ((Elem*)array.buffer) + array.elements - 1; } - /// @returns pointer to last element; undefined behaviour if array is empty. + /// @returns pointer to last element const Elem *back() const { - DBUG_ASSERT(array.elements >= 1); - return ((const Elem*)array.buffer) + (array.elements - 1); + return ((const Elem*)array.buffer) + array.elements - 1; } /** @@ -167,6 +164,11 @@ public: return (insert_dynamic(&array, (uchar*)&el)); } + bool push(Elem &el) + { + return append(el); + } + /// Pops the last element. Does nothing if array is empty. Elem& pop() { @@ -199,6 +201,23 @@ public: set_dynamic(&array, &el, idx); } + bool resize(size_t new_size, Elem default_val) + { + size_t old_size= elements(); + if (allocate_dynamic(&array, new_size)) + return true; + + if (new_size > old_size) + { + set_dynamic(&array, (uchar*)&default_val, new_size - 1); + /*for (size_t i= old_size; i != new_size; i++) + { + at(i)= default_val; + }*/ + } + return false; + } + ~Dynamic_array() { delete_dynamic(&array); @@ -210,6 +229,12 @@ public: { my_qsort(array.buffer, array.elements, sizeof(Elem), (qsort_cmp)cmp_func); } + + typedef int (*CMP_FUNC2)(const Elem *el1, const Elem *el2, void *); + void sort(CMP_FUNC2 cmp_func, void *data) + { + my_qsort2(array.buffer, array.elements, sizeof(Elem), (qsort2_cmp)cmp_func, data); + } }; #endif /* SQL_ARRAY_INCLUDED */ diff --git a/sql/sql_audit.cc b/sql/sql_audit.cc index 07a5243e836..f0e35cb022f 100644 --- a/sql/sql_audit.cc +++ b/sql/sql_audit.cc @@ -82,6 +82,9 @@ static void general_class_handler(THD *thd, uint event_subtype, va_list ap) event.general_query_length= va_arg(ap, unsigned int); event.general_charset= va_arg(ap, struct charset_info_st *); event.general_rows= (unsigned long long) va_arg(ap, ha_rows); + event.database= va_arg(ap, const char *); + event.database_length= va_arg(ap, unsigned int); + event.query_id= (unsigned long long) thd->query_id; event_class_dispatch(thd, MYSQL_AUDIT_GENERAL_CLASS, &event); } @@ -131,6 +134,7 @@ static void table_class_handler(THD *thd, uint event_subclass, va_list ap) event.new_database_length= va_arg(ap, unsigned int); event.new_table= va_arg(ap, const char *); event.new_table_length= va_arg(ap, unsigned int); + event.query_id= (unsigned long long) thd->query_id; event_class_dispatch(thd, MYSQL_AUDIT_TABLE_CLASS, &event); } diff --git a/sql/sql_audit.h b/sql/sql_audit.h index 1c7d6a1c224..bf8d837580a 100644 --- a/sql/sql_audit.h +++ b/sql/sql_audit.h @@ -99,7 +99,8 @@ void mysql_audit_general_log(THD *thd, time_t time, mysql_audit_notify(thd, MYSQL_AUDIT_GENERAL_CLASS, MYSQL_AUDIT_GENERAL_LOG, 0, time, user, userlen, cmd, cmdlen, - query, querylen, clientcs, 0); + query, querylen, clientcs, (ha_rows) 0, + thd->db, thd->db_length); } } @@ -145,7 +146,8 @@ void mysql_audit_general(THD *thd, uint event_subtype, mysql_audit_notify(thd, MYSQL_AUDIT_GENERAL_CLASS, event_subtype, error_code, time, user, userlen, msg, msglen, - query.str(), query.length(), query.charset(), rows); + query.str(), query.length(), query.charset(), rows, + thd->db, thd->db_length); } } @@ -176,10 +178,18 @@ 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, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + 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); } } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 31a51fefc00..733c79f3e06 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -57,6 +57,7 @@ #include "sql_table.h" // build_table_filename #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 @@ -648,11 +649,24 @@ bool close_cached_connection_tables(THD *thd, LEX_STRING *connection) static void mark_temp_tables_as_free_for_reuse(THD *thd) { + DBUG_ENTER("mark_temp_tables_as_free_for_reuse"); + + 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(); + if (thd->rgi_slave) + { + /* + Temporary tables are shared with other by sql execution threads. + As a safety messure, clear the pointer to the common area. + */ + thd->temporary_tables= 0; + } + DBUG_VOID_RETURN; } @@ -666,6 +680,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; @@ -696,6 +711,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; } @@ -1035,6 +1051,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) @@ -1049,6 +1069,7 @@ bool close_temporary_tables(THD *thd) if (!thd->temporary_tables) DBUG_RETURN(FALSE); + DBUG_ASSERT(!thd->rgi_slave); if (!mysql_bin_log.is_open()) { @@ -1483,26 +1504,16 @@ 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 *key; + const char *tmp_key; + char key[MAX_DBKEY_LENGTH]; uint key_length; - char key_suffix[TMP_TABLE_KEY_EXTRA]; - TABLE *table; - key_length= get_table_def_key(tl, &key); - - int4store(key_suffix, thd->variables.server_id); - int4store(key_suffix + 4, thd->variables.pseudo_thread_id); + 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); - 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) && - !memcmp(table->s->table_cache_key.str + key_length, key_suffix, - TMP_TABLE_KEY_EXTRA)) - return table; - } - return NULL; + return find_temporary_table(thd, key, key_length + TMP_TABLE_KEY_EXTRA); } @@ -1516,16 +1527,42 @@ 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; + /* + 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. + */ + table->file->unbind_psi(); + table->file->rebind_psi(); + } +#endif + } + result= table; + break; } } - - return NULL; + thd->unlock_temporary_tables(); + return result; } @@ -1574,6 +1611,9 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans) /* 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); } @@ -1602,6 +1642,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; @@ -1621,12 +1662,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, &thread_running_lock); + table->in_use= 0; // No statistics } + thd->unlock_temporary_tables(); close_temporary(table, free_share, delete_table); DBUG_VOID_RETURN; } @@ -3912,10 +3955,9 @@ end: DBUG_RETURN(error); } -extern "C" uchar *schema_set_get_key(const uchar *record, size_t *length, +extern "C" uchar *schema_set_get_key(const TABLE_LIST *table, size_t *length, my_bool not_used __attribute__((unused))) { - TABLE_LIST *table=(TABLE_LIST*) record; *length= table->db_length; return (uchar*) table->db; } @@ -3956,7 +3998,7 @@ lock_table_names(THD *thd, MDL_request_list mdl_requests; TABLE_LIST *table; MDL_request global_request; - Hash_set<TABLE_LIST, schema_set_get_key> schema_set; + Hash_set<TABLE_LIST> schema_set(schema_set_get_key); ulong org_lock_wait_timeout= lock_wait_timeout; /* Check if we are using CREATE TABLE ... IF NOT EXISTS */ bool create_table; @@ -4002,7 +4044,7 @@ lock_table_names(THD *thd, Scoped locks: Take intention exclusive locks on all involved schemas. */ - Hash_set<TABLE_LIST, schema_set_get_key>::Iterator it(schema_set); + Hash_set<TABLE_LIST>::Iterator it(schema_set); while ((table= it++)) { MDL_request *schema_request= new (thd->mem_root) MDL_request; @@ -5427,14 +5469,18 @@ TABLE *open_table_uncached(THD *thd, handlerton *hton, 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, &thread_running_lock); + } + thd->unlock_temporary_tables(); } tmp_table->pos_in_table_list= 0; DBUG_PRINT("tmptable", ("opened table: '%s'.'%s' 0x%lx", tmp_table->s->db.str, @@ -5589,9 +5635,9 @@ bool open_temporary_table(THD *thd, TABLE_LIST *tl) */ DBUG_ASSERT(!tl->derived && !tl->schema_table); - if (tl->open_type == OT_BASE_ONLY) + if (tl->open_type == OT_BASE_ONLY || !thd->have_temporary_tables()) { - DBUG_PRINT("info", ("skip_temporary is set")); + DBUG_PRINT("info", ("skip_temporary is set or no temporary tables")); DBUG_RETURN(FALSE); } @@ -8275,7 +8321,6 @@ int setup_conds(THD *thd, TABLE_LIST *tables, List<TABLE_LIST> &leaves, embedded->on_expr->fix_fields(thd, &embedded->on_expr)) || embedded->on_expr->check_cols(1)) goto err_no_arena; - select_lex->cond_count++; } /* If it's a semi-join nest, fix its "left expression", as it is used by diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc index 3bb5deab406..4d91dbab9a6 100644 --- a/sql/sql_binlog.cc +++ b/sql/sql_binlog.cc @@ -80,6 +80,8 @@ void mysql_client_binlog_statement(THD* thd) my_bool have_fd_event= TRUE; int err; Relay_log_info *rli; + rpl_group_info *rgi; + rli= thd->rli_fake; if (!rli) { @@ -95,6 +97,9 @@ 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)); @@ -111,14 +116,15 @@ void mysql_client_binlog_statement(THD* thd) goto end; } - rli->sql_thd= thd; + rli->sql_driver_thd= thd; rli->no_storage= TRUE; 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 +238,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) | @@ -267,7 +273,7 @@ void mysql_client_binlog_statement(THD* thd) end: 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 5e86a889053..7e163b0dbcc 100644 --- a/sql/sql_bitmap.h +++ b/sql/sql_bitmap.h @@ -72,6 +72,7 @@ public: bool is_subset(const Bitmap& map2) const { return bitmap_is_subset(&map, &map2.map); } bool is_overlapping(const Bitmap& map2) const { return bitmap_is_overlapping(&map, &map2.map); } bool operator==(const Bitmap& map2) const { return bitmap_cmp(&map, &map2.map); } + bool operator!=(const Bitmap& map2) const { return !(*this == map2); } char *print(char *buf) const { char *s=buf; diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index d70113c28e4..97034878eef 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1320,17 +1320,17 @@ ulong Query_cache::resize(ulong query_cache_size_arg) { BLOCK_LOCK_WR(block); Query_cache_query *query= block->query(); - if (query && query->writer()) + if (query->writer()) { /* - Drop the writer; this will cancel any attempts to store + Drop the writer; this will cancel any attempts to store the processed statement associated with this writer. */ query->writer()->first_query_block= NULL; query->writer(0); refused++; } - BLOCK_UNLOCK_WR(block); + query->unlock_n_destroy(); block= block->next; } while (block != queries_blocks); } @@ -2132,9 +2132,13 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } #endif /*!EMBEDDED_LIBRARY*/ - 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->get_sent_row_count()) + status_var_increment(thd->status_var.empty_queries); + else + status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count()); /* End the statement transaction potentially started by an @@ -4307,11 +4311,11 @@ my_bool Query_cache::move_by_type(uchar **border, size_t key_length; key=query_cache_query_get_key((uchar*) block, &key_length, 0); my_hash_first(&queries, (uchar*) key, key_length, &record_idx); - // Move table of used tables - memmove((char*) new_block->table(0), (char*) block->table(0), - ALIGN_SIZE(n_tables*sizeof(Query_cache_block_table))); block->query()->unlock_n_destroy(); block->destroy(); + // Move table of used tables + memmove((char*) new_block->table(0), (char*) block->table(0), + ALIGN_SIZE(n_tables*sizeof(Query_cache_block_table))); new_block->init(len); new_block->type=Query_cache_block::QUERY; new_block->used=used; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7d87e245035..40455a0f474 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -662,6 +662,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) { @@ -914,7 +925,7 @@ 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* bf_thd, THD *thd, my_bool signal) +extern "C" void wsrep_thd_awake(THD *thd, my_bool signal) { if (signal) { @@ -991,7 +1002,7 @@ THD::THD() #endif :Statement(&main_lex, &main_mem_root, STMT_CONVENTIONAL_EXECUTION, /* statement id */ 0), - rli_fake(0), rli_slave(NULL), + rli_fake(0), rgi_fake(0), rgi_slave(NULL), in_sub_stmt(0), log_all_errors(0), binlog_unsafe_warning_flags(0), binlog_table_maps(0), @@ -1022,12 +1033,17 @@ THD::THD() wsrep_applier(is_applier), wsrep_applier_closing(FALSE), wsrep_client_thread(0), + wsrep_po_handle(WSREP_PO_INITIALIZER), + wsrep_po_cnt(0), + wsrep_po_in_trans(FALSE), + wsrep_apply_format(0), wsrep_apply_toi(false), #endif m_parser_state(NULL), #if defined(ENABLED_DEBUG_SYNC) debug_sync_control(0), #endif /* defined(ENABLED_DEBUG_SYNC) */ + wait_for_commit_ptr(0), main_da(0, false, false), m_stmt_da(&main_da) { @@ -1203,7 +1219,7 @@ THD::THD() #endif /* WITH_WSREP */ m_internal_handler= NULL; - m_binlog_invoker= FALSE; + m_binlog_invoker= INVOKER_NONE; arena_for_cached_items= 0; memset(&invoker_user, 0, sizeof(invoker_user)); memset(&invoker_host, 0, sizeof(invoker_host)); @@ -1541,6 +1557,7 @@ void THD::init(void) reset_binlog_local_stmt_filter(); set_status_var_init(); bzero((char *) &org_status_var, sizeof(org_status_var)); + start_bytes_received= 0; #ifdef WITH_WSREP wsrep_exec_mode= wsrep_applier ? REPL_RECV : LOCAL_STATE; wsrep_conflict_state= NO_CONFLICT; @@ -1551,6 +1568,7 @@ void THD::init(void) wsrep_converted_lock_session= false; wsrep_retry_counter= 0; wsrep_rli= NULL; + wsrep_rgi= NULL; wsrep_PA_safe= true; wsrep_consistency_check = NO_CONSISTENCY_CHECK; wsrep_mysql_replicated = 0; @@ -1760,6 +1778,7 @@ THD::~THD() mysql_mutex_unlock(&LOCK_wsrep_thd); mysql_mutex_destroy(&LOCK_wsrep_thd); if (wsrep_rli) delete wsrep_rli; + if (wsrep_rgi) delete wsrep_rgi; if (wsrep_status_vars) wsrep->stats_free(wsrep, wsrep_status_vars); #endif /* Close connection */ @@ -1788,6 +1807,11 @@ THD::~THD() dbug_sentry= THD_SENTRY_GONE; #endif #ifndef EMBEDDED_LIBRARY + if (rgi_fake) + { + delete rgi_fake; + rgi_fake= NULL; + } if (rli_fake) { delete rli_fake; @@ -1795,8 +1819,8 @@ THD::~THD() } mysql_audit_free_thd(this); - if (rli_slave) - rli_slave->cleanup_after_session(); + if (rgi_slave) + rgi_slave->cleanup_after_session(); #endif free_root(&main_mem_root, MYF(0)); @@ -2226,7 +2250,7 @@ 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 } @@ -2253,7 +2277,7 @@ 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) { @@ -2263,8 +2287,8 @@ void THD::cleanup_after_query() #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; @@ -2459,6 +2483,7 @@ int THD::send_explain_fields(select_result *result) { List<Item> field_list; make_explain_field_list(field_list); + result->prepare(field_list, NULL); return (result->send_result_set_metadata(field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); @@ -2721,7 +2746,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); @@ -3878,7 +3904,8 @@ select_materialize_with_stats:: create_result_table(THD *thd_arg, List<Item> *column_types, bool is_union_distinct, ulonglong options, const char *table_alias, bool bit_fields_as_long, - bool create_table) + bool create_table, + bool keep_row_order) { DBUG_ASSERT(table == 0); tmp_table_param.field_count= column_types->elements; @@ -3886,7 +3913,8 @@ 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))) + options, HA_POS_ERROR, (char*) table_alias, + keep_row_order))) return TRUE; col_stat= (Column_statistics*) table->in_use->alloc(table->s->fields * @@ -4020,7 +4048,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; @@ -4895,9 +4923,9 @@ void THD::leave_locked_tables_mode() 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) if (slave_thread && has_invoker()) { @@ -4909,7 +4937,7 @@ void THD::get_definer(LEX_USER *definer) } else #endif - get_default_definer(this, definer); + get_default_definer(this, definer, role); } @@ -6296,6 +6324,273 @@ 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() +{ + rgi_slave->rli->save_temporary_tables= temporary_tables; + mysql_mutex_unlock(&rgi_slave->rli->data_lock); +} + +bool THD::rgi_have_temporary_tables() +{ + return rgi_slave->rli->save_temporary_tables != 0; +} + + +wait_for_commit::wait_for_commit() + : subsequent_commits_list(0), next_subsequent_commit(0), waitee(0), + opaque_pointer(0), + waiting_for_commit(false), wakeup_error(0), + wakeup_subsequent_commits_running(false) +{ + 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); +} + + +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); + waiting_for_commit= false; + 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) +{ + waiting_for_commit= true; + wakeup_error= 0; + DBUG_ASSERT(!this->waitee /* No prior registration allowed */); + 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) + waiting_for_commit= false; + 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() +{ + mysql_mutex_lock(&LOCK_wait_commit); + while (waiting_for_commit) + mysql_cond_wait(&COND_wait_commit, &LOCK_wait_commit); + mysql_mutex_unlock(&LOCK_wait_commit); + waitee= NULL; + 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; +} + + +/* Cancel a previously registered wait for another THD to commit before us. */ +void +wait_for_commit::unregister_wait_for_prior_commit2() +{ + mysql_mutex_lock(&LOCK_wait_commit); + if (waiting_for_commit) + { + wait_for_commit *loc_waitee= this->waitee; + wait_for_commit **next_ptr_ptr, *cur; + 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 (waiting_for_commit) + mysql_cond_wait(&COND_wait_commit, &LOCK_wait_commit); + } + else + { + /* Remove ourselves from the list in the waitee. */ + next_ptr_ptr= &loc_waitee->subsequent_commits_list; + while ((cur= *next_ptr_ptr) != NULL) + { + if (cur == this) + { + *next_ptr_ptr= this->next_subsequent_commit; + break; + } + next_ptr_ptr= &cur->next_subsequent_commit; + } + waiting_for_commit= false; + mysql_mutex_unlock(&loc_waitee->LOCK_wait_commit); + } + } + mysql_mutex_unlock(&LOCK_wait_commit); + this->waitee= NULL; +} + + bool Discrete_intervals_list::append(ulonglong start, ulonglong val, ulonglong incr) { diff --git a/sql/sql_class.h b/sql/sql_class.h index 946d69a2759..cfce0f81621 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -71,6 +71,7 @@ struct wsrep_thd_shadow { #endif class Reprepare_observer; class Relay_log_info; +struct rpl_group_info; class Rpl_filter; class Query_log_event; @@ -468,7 +469,8 @@ extern int killed_errno(killed_state killed); enum killed_type { KILL_TYPE_ID, - KILL_TYPE_USER + KILL_TYPE_USER, + KILL_TYPE_QUERY }; #include "sql_lex.h" /* Must be here */ @@ -761,6 +763,35 @@ typedef struct system_status_var void mark_transaction_to_rollback(THD *thd, bool all); + +/** + 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; +} + + #ifdef MYSQL_SERVER void free_tmp_table(THD *thd, TABLE *entry); @@ -1083,6 +1114,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 */ @@ -1596,6 +1629,120 @@ 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 flag + waiting_for_commit 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(), else NULL. */ + 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 waiting_for_commit flag is cleared when a waiter has been woken + up. The COND_wait_commit condition is signalled when this has been + cleared. + */ + bool waiting_for_commit; + /* 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; + + void register_wait_for_prior_commit(wait_for_commit *waitee); + int wait_for_prior_commit() + { + /* + 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 (waiting_for_commit) + return wait_for_prior_commit2(); + else + return wakeup_error; + } + void wakeup_subsequent_commits(int wakeup_error) + { + /* + 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); + } + void unregister_wait_for_prior_commit() + { + if (waiting_for_commit) + unregister_wait_for_prior_commit2(); + } + + void wakeup(int wakeup_error); + + int wait_for_prior_commit2(); + void wakeup_subsequent_commits2(int wakeup_error); + void unregister_wait_for_prior_commit2(); + + wait_for_commit(); + ~wait_for_commit(); +}; + + extern "C" void my_message_sql(uint error, const char *str, myf MyFlags); class THD; @@ -1631,13 +1778,14 @@ 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; /* Used to SLAVE SQL thread */ Rpl_filter* rpl_filter; - void reset_for_next_command(bool calculate_userstat); + void reset_for_next_command(); /* Constant for THD::where initialization in the beginning of every query. @@ -2549,6 +2697,7 @@ public: wsrep_trx_meta_t wsrep_trx_meta; uint32 wsrep_rand; Relay_log_info* wsrep_rli; + rpl_group_info* wsrep_rgi; bool wsrep_converted_lock_session; wsrep_ws_handle_t wsrep_ws_handle; #ifdef WSREP_PROC_INFO @@ -2566,6 +2715,13 @@ public: 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; + my_bool wsrep_po_in_trans; +#ifdef GTID_SUPPORT + rpl_sid wsrep_po_sid; +#endif /* GTID_SUPPORT */ + void* wsrep_apply_format; bool wsrep_apply_toi; /* applier processing in TOI */ #endif /* WITH_WSREP */ /** @@ -3415,9 +3571,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; @@ -3467,6 +3625,25 @@ public: void wait_for_wakeup_ready(); /* Wake this thread up from wait_for_wakeup_ready(). */ void signal_wakeup_ready(); + + wait_for_commit *wait_for_commit_ptr; + int wait_for_prior_commit() + { + if (wait_for_commit_ptr) + { + int err= wait_for_commit_ptr->wait_for_prior_commit(); + if (err) + my_error(ER_PRIOR_COMMIT_FAILED, MYF(0)); + return err; + } + return 0; + } + void wakeup_subsequent_commits(int wakeup_error) + { + if (wait_for_commit_ptr) + wait_for_commit_ptr->wakeup_subsequent_commits(wakeup_error); + } + private: /** The current internal error handler for this thread, or NULL. */ @@ -3492,14 +3669,15 @@ private: 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. @@ -3519,6 +3697,27 @@ private: bool wakeup_ready; mysql_mutex_t LOCK_wakeup_ready; mysql_cond_t COND_wakeup_ready; + + /* Protect against add/delete of temporary tables in parallel replication */ + void rgi_lock_temporary_tables(); + void rgi_unlock_temporary_tables(); + bool rgi_have_temporary_tables(); +public: + inline void lock_temporary_tables() + { + if (rgi_slave) + rgi_lock_temporary_tables(); + } + inline void unlock_temporary_tables() + { + if (rgi_slave) + rgi_unlock_temporary_tables(); + } + inline bool have_temporary_tables() + { + return (temporary_tables || + (rgi_slave && rgi_have_temporary_tables())); + } }; @@ -3659,6 +3858,11 @@ public: void begin_dataset() {} #endif virtual void update_used_tables() {} + + void reset_offset_limit() + { + unit->offset_limit_cnt= 0; + } }; @@ -3687,6 +3891,26 @@ public: }; +/* + This is a select_result_sink which stores the data in text form. +*/ + +class select_result_text_buffer : public select_result_sink +{ +public: + select_result_text_buffer(THD *thd_arg) : thd(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); + + THD *thd; + List<char*> rows; + int n_columns; +}; + /* Base class for select_result descendands which intercept and @@ -3999,7 +4223,8 @@ public: bool is_distinct, ulonglong options, const char *alias, bool bit_fields_as_long, - bool create_table); + bool create_table, + bool keep_row_order= FALSE); TMP_TABLE_PARAM *get_tmp_table_param() { return &tmp_table_param; } }; @@ -4069,7 +4294,8 @@ public: bool is_distinct, ulonglong options, const char *alias, bool bit_fields_as_long, - bool create_table); + bool create_table, + bool keep_row_order= FALSE); bool init_result_table(ulonglong select_options); int send_data(List<Item> &items); void cleanup(); @@ -4412,7 +4638,9 @@ class multi_update :public select_result_interceptor so that afterward send_error() 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, List<Item> *fields, List<Item> *values, diff --git a/sql/sql_cmd.h b/sql/sql_cmd.h index de7ef5fc832..231db2a1d8c 100644 --- a/sql/sql_cmd.h +++ b/sql/sql_cmd.h @@ -93,6 +93,7 @@ enum enum_sql_command { SQLCOM_SHOW_CLIENT_STATS, 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, /* When a command is added here, be sure it's also added in mysqld.cc diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index c1820c0187e..a38077c40cb 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -393,7 +393,7 @@ bool Select_materialize::send_result_set_metadata(List<Item> &list, uint flags) if (create_result_table(unit->thd, unit->get_unit_column_types(), FALSE, thd->variables.option_bits | TMP_TABLE_ALL_COLUMNS, - "", FALSE, TRUE)) + "", FALSE, TRUE, TRUE)) return TRUE; materialized_cursor= new (&table->mem_root) diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 9e30ed4513e..8db305e45c5 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -992,7 +992,7 @@ static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp, /* first, get the list of tables */ Dynamic_array<LEX_STRING*> files(dirp->number_of_files); - Discovered_table_list tl(thd, &files, &null_lex_str); + Discovered_table_list tl(thd, &files); if (ha_discover_table_names(thd, &db, dirp, &tl, true)) DBUG_RETURN(1); @@ -1471,14 +1471,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) && diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 1d52be4272a..840fd4947a6 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -40,6 +40,161 @@ #include "records.h" // init_read_record, #include "sql_derived.h" // mysql_handle_list_of_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. +*/ + +void Delete_plan::save_explain_data(Explain_query *query) +{ + Explain_delete* explain= new Explain_delete; + + 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(query, explain); + } + + query->add_upd_del_plan(explain); +} + + +void Update_plan::save_explain_data(Explain_query *query) +{ + Explain_update* explain= new Explain_update; + save_explain_data_intern(query, explain); + query->add_upd_del_plan(explain); +} + + +void Update_plan::save_explain_data_intern(Explain_query *query, + Explain_update *explain) +{ + 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; + } + + 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(part_info, &explain->used_partitions); + 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= test(select && select->cond); + explain->using_filesort= using_filesort; + explain->using_io_buffer= using_io_buffer; + + make_possible_keys_line(table, possible_keys, &explain->possible_keys_line); + + 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_str.append(table->key_info[index].name); + char buf[64]; + size_t length; + length= longlong10_to_str(table->key_info[index].key_length, buf, 10) - buf; + explain->key_len_str.append(buf, 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. @@ -49,7 +204,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; @@ -63,12 +219,16 @@ 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(); + 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); @@ -90,10 +250,16 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } 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= 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); + (void) result->prepare(select_lex->item_list, NULL); + if (thd->lex->current_select->first_cond_optimization) { thd->lex->current_select->save_leaf_tables(thd); @@ -155,14 +321,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 exit_without_my_ok; + if (!(error=table->file->ha_delete_all_rows())) { /* @@ -187,14 +358,23 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, Item::cond_result result; conds= remove_eq_conds(thd, conds, &result); if (result == Item::COND_FALSE) // Impossible where + { limit= 0; + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + } } #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) + goto exit_without_my_ok; + my_ok(thd, 0); DBUG_RETURN(0); } @@ -211,6 +391,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) + goto exit_without_my_ok; + delete select; free_underlaid_joins(thd, select_lex); /* @@ -241,28 +425,54 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, 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; - ha_rows found_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 exit_without_my_ok; + + query_plan.save_explain_data(thd->lex->explain); + + DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", + dbug_serve_apcs(thd, 1);); + + 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 | MY_THREAD_SPECIFIC)); @@ -296,7 +506,7 @@ 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)) { if (init_read_record(&info, thd, table, select, 1, 1, FALSE)) { @@ -306,7 +516,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } } else - init_read_record_idx(&info, thd, table, 1, usable_index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); init_ftfuncs(thd, select_lex, 1); THD_STAGE_INFO(thd, stage_updating); @@ -326,9 +536,16 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, 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; + } + while (!(error=info.read_record(&info)) && !thd->killed && ! thd->is_error()) { @@ -348,6 +565,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++; @@ -458,10 +681,23 @@ cleanup: if (error < 0 || (thd->lex->ignore && !thd->is_error() && !thd->is_fatal_error)) { - my_ok(thd, deleted); + if (!with_select) + my_ok(thd, deleted); + else + result->send_eof(); DBUG_PRINT("info",("%ld records deleted",(long) deleted)); } DBUG_RETURN(error >= 0 || thd->is_error()); + + /* Special exits */ +exit_without_my_ok: + query_plan.save_explain_data(thd->lex->explain); + int err2= thd->lex->explain->send_explain(thd); + + delete select; + free_underlaid_joins(thd, select_lex); + //table->set_keyread(false); + DBUG_RETURN((err2 || thd->is_error() || thd->killed) ? 1 : 0); } @@ -472,13 +708,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; @@ -490,7 +729,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, 0) || setup_conds(thd, table_list, select_lex->leaf_tables, conds) || setup_ftfuncs(select_lex)) DBUG_RETURN(TRUE); 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 d9dd538f96d..712b1606c11 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -395,8 +395,6 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived) if (dt_select->options & OPTION_SCHEMA_TABLE) parent_lex->options |= OPTION_SCHEMA_TABLE; - parent_lex->cond_count+= dt_select->cond_count; - if (!derived->get_unit()->prepared) { dt_select->leaf_tables.empty(); @@ -619,7 +617,17 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) { sl->context.outer_context= 0; // Prepare underlying views/DT first. - sl->handle_derived(lex, DT_PREPARE); + if ((res= sl->handle_derived(lex, DT_PREPARE))) + goto exit; + + if (derived->outer_join) + { + /* Mark that table is part of OUTER JOIN and fields may be NULL */ + for (TABLE_LIST *cursor= (TABLE_LIST*) sl->table_list.first; + cursor; + cursor= cursor->next_local) + cursor->outer_join|= JOIN_TYPE_OUTER; + } } unit->derived= derived; @@ -714,6 +722,10 @@ exit: /* Add new temporary table to list of open derived tables */ table->next= thd->derived_tables; thd->derived_tables= table; + + /* If table is used by a left join, mark that any column may be null */ + if (derived->outer_join) + table->maybe_null= 1; } if (arena) thd->restore_active_arena(arena, &backup); diff --git a/sql/sql_error.h b/sql/sql_error.h index 4c1ebf432c4..14338ee041d 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -565,6 +565,8 @@ 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(const char *str_arg, CHARSET_INFO *cs_arg) + : ErrConv(), str(str_arg), len(strlen(str_arg)), cs(cs_arg) {} ErrConvString(String *s) : ErrConv(), str(s->ptr()), len(s->length()), cs(s->charset()) {} const char *ptr() const diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc new file mode 100644 index 00000000000..84ff8759d96 --- /dev/null +++ b/sql/sql_explain.cc @@ -0,0 +1,955 @@ +/* + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "sql_priv.h" +#include "sql_select.h" + + +Explain_query::Explain_query(THD *thd_arg) : + upd_del_plan(NULL), insert_plan(NULL), thd(thd_arg), apc_enabled(false) +{ + operations= 0; +} + + +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 select_send()) || + thd->send_explain_fields(result)) + return 1; + + int res; + if ((res= print_explain(result, lex->describe))) + 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) +{ + if (upd_del_plan) + { + upd_del_plan->print_explain(this, output, explain_flags); + return 0; + } + else if (insert_plan) + { + insert_plan->print_explain(this, output, explain_flags); + 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); + } +} + + +bool print_explain_query(LEX *lex, THD *thd, String *str) +{ + return lex->explain->print_explain_str(thd, str); +} + + +/* + Return tabular EXPLAIN output as a text string +*/ + +bool Explain_query::print_explain_str(THD *thd, String *out_str) +{ + List<Item> fields; + thd->make_explain_field_list(fields); + + 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)) + return true; + output_buf.save_to(out_str); + return false; +} + + +static void push_str(List<Item> *item_list, const char *str) +{ + item_list->push_back(new Item_string(str, + strlen(str), system_charset_info)); +} + + +static void push_string(List<Item> *item_list, String *str) +{ + item_list->push_back(new Item_string(str->ptr(), str->length(), + system_charset_info)); +} + + +int Explain_union::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags) +{ + 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); + } + + /* Print a line with "UNION RESULT" */ + List<Item> item_list; + Item *item_null= new Item_null(); + + /* `id` column */ + item_list.push_back(item_null); + + /* `select_type` column */ + push_str(&item_list, fake_select_type); + + /* `table` column: something like "<union1,2>" */ + { + uint childno= 0; + uint len= 6, lastop= 0; + memcpy(table_name_buffer, STRING_WITH_LEN("<union")); + + for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN; + childno++) + { + len+= lastop; + lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len, + "%u,", union_members.at(childno)); + } + + if (childno < union_members.elements() || len + lastop >= NAME_LEN) + { + memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); + len+= 4; + } + else + { + len+= lastop; + table_name_buffer[len - 1]= '>'; // change ',' to '>' + } + const CHARSET_INFO *cs= system_charset_info; + item_list.push_back(new Item_string(table_name_buffer, len, cs)); + } + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + + /* `type` column */ + push_str(&item_list, join_type_str[JT_ALL]); + + /* `possible_keys` column */ + 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); + + /* `rows` */ + item_list.push_back(item_null); + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + /* `Extra` */ + StringBuffer<256> extra_buf; + if (using_filesort) + { + extra_buf.append(STRING_WITH_LEN("Using filesort")); + } + const CHARSET_INFO *cs= system_charset_info; + item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); + + //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); +} + + +/* + 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) +{ + 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)) + return 1; + } + return 0; +} + + +Explain_select::~Explain_select() +{ + if (join_tabs) + { + for (uint i= 0; i< n_join_tabs; i++) + delete join_tabs[i]; + my_free(join_tabs); + } +} + + +int Explain_select::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags) +{ + if (message) + { + List<Item> item_list; + const CHARSET_INFO *cs= system_charset_info; + Item *item_null= new Item_null(); + + item_list.push_back(new Item_int((int32) select_id)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); + for (uint i=0 ; i < 7; i++) + item_list.push_back(item_null); + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + if (explain_flags & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + item_list.push_back(new Item_string(message,strlen(message),cs)); + + if (output->send_data(item_list)) + return 1; + } + else + { + bool using_tmp= using_temporary; + bool using_fs= using_filesort; + for (uint i=0; i< n_join_tabs; i++) + { + join_tabs[i]->print_explain(output, explain_flags, 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; + } + } + } + + return print_explain_for_children(query, output, explain_flags); +} + + +void Explain_table_access::push_extra(enum explain_extra_tag extra_tag) +{ + extra_tags.append(extra_tag); +} + + +int Explain_table_access::print_explain(select_result_sink *output, uint8 explain_flags, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort) +{ + const CHARSET_INFO *cs= system_charset_info; + const char *hash_key_prefix= "#hash#"; + bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || + type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); + + List<Item> item_list; + Item *item_null= new Item_null(); + + if (sjm_nest_select_id) + select_id= sjm_nest_select_id; + + /* `id` column */ + item_list.push_back(new Item_int((int32) select_id)); + + /* `select_type` column */ + if (sjm_nest_select_id) + push_str(&item_list, "MATERIALIZED"); + else + push_str(&item_list, select_type); + + /* `table` column */ + push_string(&item_list, &table_name); + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + { + if (used_partitions_set) + { + push_string(&item_list, &used_partitions); + } + else + item_list.push_back(item_null); + } + + /* `type` column */ + push_str(&item_list, join_type_str[type]); + + /* `possible_keys` column */ + if (possible_keys_str.length() > 0) + push_string(&item_list, &possible_keys_str); + else + item_list.push_back(item_null); + + /* `key` */ + StringBuffer<64> key_str; + 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; + quick_info->print_key(&buf2); + key_str.append(buf2); + } + if (type == JT_HASH_NEXT) + key_str.append(hash_next_key.get_key_name()); + + if (key_str.length() > 0) + push_string(&item_list, &key_str); + else + item_list.push_back(item_null); + + /* `key_len` */ + StringBuffer<64> key_len_str; + + 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); + } + + if (key_len_str.length() > 0) + push_string(&item_list, &key_len_str); + else + item_list.push_back(item_null); + + /* `ref` */ + if (ref_set) + push_string(&item_list, &ref); + else + item_list.push_back(item_null); + + /* `rows` */ + if (rows_set) + { + item_list.push_back(new Item_int((longlong) (ulonglong) rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED) + { + if (filtered_set) + { + item_list.push_back(new Item_float(filtered, 2)); + } + else + item_list.push_back(item_null); + } + + /* `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 Item_string(extra_buf.ptr(), extra_buf.length(), cs)); + + if (output->send_data(item_list)) + return 1; + + return 0; +} + + +/* + 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_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(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_extra_recursive(String *str) +{ + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || + quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC) + { + 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) +{ + if (deleting_all_rows) + { + const char *msg= "Deleting all rows"; + int res= print_explain_message_line(output, explain_flags, + 1 /*select number*/, + select_type, &rows, msg); + return res; + + } + else + { + return Explain_update::print_explain(query, output, explain_flags); + } +} + + +int Explain_update::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags) +{ + StringBuffer<64> key_buf; + StringBuffer<64> key_len_buf; + StringBuffer<64> extra_str; + if (impossible_where || no_partitions) + { + const char *msg= impossible_where ? + "Impossible WHERE" : + "No matching rows after partition pruning"; + int res= print_explain_message_line(output, explain_flags, + 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 + { + key_buf.copy(key_str); + key_len_buf.copy(key_len_str); + } + + 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 (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) + */ + + print_explain_row(output, explain_flags, + 1, /* id */ + select_type, + table_name.c_ptr(), + used_partitions_set? used_partitions.c_ptr() : NULL, + jtype, + possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, + 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, + extra_str.c_ptr_safe()); + + return print_explain_for_children(query, output, explain_flags); +} + + +int Explain_insert::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags) +{ + const char *select_type="INSERT"; + print_explain_row(output, explain_flags, + 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); + + return print_explain_for_children(query, output, explain_flags); +} + + +void delete_explain_query(LEX *lex) +{ + delete lex->explain; + lex->explain= NULL; +} + + +void create_explain_query(LEX *lex, MEM_ROOT *mem_root) +{ + DBUG_ASSERT(!lex->explain); + lex->explain= new Explain_query(lex->thd); + DBUG_ASSERT(mem_root == current_thd->mem_root); + lex->explain->mem_root= mem_root; +} + +void create_explain_query_if_not_exists(LEX *lex, MEM_ROOT *mem_root) +{ + if (!lex->explain) + create_explain_query(lex, mem_root); +} + diff --git a/sql/sql_explain.h b/sql/sql_explain.h new file mode 100644 index 00000000000..b9f381b867b --- /dev/null +++ b/sql/sql_explain.h @@ -0,0 +1,550 @@ +/* + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +/************************************************************************************** + + 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: + enum explain_node_type + { + EXPLAIN_UNION, + EXPLAIN_SELECT, + EXPLAIN_UPDATE, + EXPLAIN_DELETE, + EXPLAIN_INSERT + }; + + virtual enum explain_node_type get_type()= 0; + virtual int get_select_id()= 0; + + /* + 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)=0; + + int print_explain_for_children(Explain_query *query, select_result_sink *output, + uint8 explain_flags); + virtual ~Explain_node(){} +}; + + +class Explain_table_access; + + +/* + 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_node +{ +public: + enum explain_node_type get_type() { return EXPLAIN_SELECT; } + + Explain_select() : + message(NULL), join_tabs(NULL), + using_temporary(false), using_filesort(false) + {} + + ~Explain_select(); + + bool add_table(Explain_table_access *tab) + { + if (!join_tabs) + { + join_tabs= (Explain_table_access**) my_malloc(sizeof(Explain_table_access*) * + MAX_TABLES, MYF(0)); + n_join_tabs= 0; + } + join_tabs[n_join_tabs++]= tab; + return false; + } + +public: + int select_id; + const char *select_type; + + int get_select_id() { return select_id; } + + /* + If message != NULL, this is a degenerate join plan, and all subsequent + members have no info + */ + const char *message; + + /* + A flat array of Explain structs for tables. The order is "just like EXPLAIN + would print them". + */ + Explain_table_access** join_tabs; + uint n_join_tabs; + + /* Global join attributes. In tabular form, they are printed on the first row */ + bool using_temporary; + bool using_filesort; + + int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags); +}; + + +/* + Explain structure for a UNION. + + A UNION may or may not have "Using filesort". +*/ + +class Explain_union : public Explain_node +{ +public: + 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); + + const char *fake_select_type; + bool using_filesort; +}; + + +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); + ~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); + + /* 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); + + /* 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; +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 +}; + + +typedef struct st_explain_bka_type +{ + bool incremental; + const char *join_alg; + StringBuffer<64> mrr_type; + +} EXPLAIN_BKA_TYPE; + + +/* + Data about how an index is used by some access method +*/ +class Explain_index_use : public Sql_alloc +{ + char *key_name; + uint key_len; + /* will add #keyparts here if we implement EXPLAIN FORMAT=JSON */ +public: + + void set(MEM_ROOT *root, const char *key_name_arg, uint key_len_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= key_len_arg; + } + + inline const char *get_key_name() { return key_name; } + inline uint get_key_len() { 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; + + /* 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); +private: + void print_extra_recursive(String *str); + const char *get_name_by_type(); +}; + + +/* + EXPLAIN data structure for a single JOIN_TAB. +*/ +class Explain_table_access : public Sql_alloc +{ +public: + void push_extra(enum explain_extra_tag extra_tag); + + /* Internals */ +public: + /* + 0 means this tab is not inside SJM nest and should use Explain_select's id + other value means the tab is inside an SJM nest. + */ + int sjm_nest_select_id; + + /* id and 'select_type' are cared-of by the parent Explain_select */ + StringBuffer<32> table_name; + + enum join_type type; + + StringBuffer<32> used_partitions; + bool used_partitions_set; + + /* Empty string means "NULL" will be printed */ + StringBuffer<32> possible_keys_str; + + /* + 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; + + bool ref_set; /* not set means 'NULL' should be printed */ + StringBuffer<32> ref; + + bool rows_set; /* not set means 'NULL' should be printed */ + ha_rows rows; + + bool filtered_set; /* not set means 'NULL' should be printed */ + 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; + + // Valid if ET_USING_INDEX_FOR_GROUP_BY is present + bool loose_scan_is_scanning; + + // valid with ET_RANGE_CHECKED_FOR_EACH_RECORD + key_map range_checked_map; + + // valid with ET_USING_MRR + StringBuffer<32> mrr_type; + + // valid with ET_USING_JOIN_BUFFER + EXPLAIN_BKA_TYPE bka_type; + + StringBuffer<32> firstmatch_table_name; + + int print_explain(select_result_sink *output, uint8 explain_flags, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort); +private: + void append_tag_name(String *str, 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. +*/ + +class Explain_update : public Explain_node +{ +public: + 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; + bool used_partitions_set; + + bool impossible_where; + bool no_partitions; + StringBuffer<64> table_name; + + enum join_type jtype; + StringBuffer<128> possible_keys_line; + StringBuffer<128> key_str; + StringBuffer<128> key_len_str; + StringBuffer<64> mrr_type; + + Explain_quick_select *quick_info; + + bool using_where; + ha_rows rows; + + bool using_filesort; + bool using_io_buffer; + + virtual int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags); +}; + + +/* + 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: + 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); +}; + + +/* + EXPLAIN data of a single-table DELETE. +*/ + +class Explain_delete: public Explain_update +{ +public: + /* + 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); +}; + + 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 ef139dfefeb..c93d1ae7a1e 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -461,7 +461,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; @@ -651,6 +652,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 Explain_insert; + 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= 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 @@ -667,6 +698,7 @@ 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; @@ -694,6 +726,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. @@ -785,6 +818,17 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, /* 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 @@ -809,10 +853,10 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, table->next_number_field=table->found_next_number_field; #ifdef HAVE_REPLICATION - if (thd->rli_slave && + if (thd->rgi_slave && (info.handle_duplicates == DUP_UPDATE) && (table->next_number_field != NULL) && - rpl_master_has_bug(thd->rli_slave, 24432, TRUE, NULL, NULL)) + rpl_master_has_bug(thd->rgi_slave->rli, 24432, TRUE, NULL, NULL)) goto abort; #endif @@ -1141,10 +1185,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); } @@ -3474,10 +3519,10 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) table->next_number_field=table->found_next_number_field; #ifdef HAVE_REPLICATION - if (thd->rli_slave && + if (thd->rgi_slave && (info.handle_duplicates == DUP_UPDATE) && (table->next_number_field != NULL) && - rpl_master_has_bug(thd->rli_slave, 24432, TRUE, NULL, NULL)) + rpl_master_has_bug(thd->rgi_slave->rli, 24432, TRUE, NULL, NULL)) DBUG_RETURN(1); #endif diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index 0acccfcee48..3b39ebf145f 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -2553,49 +2553,41 @@ 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(struct st_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= test(prev_cache); + 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,18 +2613,17 @@ static void add_mrr_explain_info(String *str, uint mrr_mode, handler *file) } } - -void JOIN_CACHE_BKA::print_explain_comment(String *str) +void JOIN_CACHE_BKA::save_explain_data(struct st_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(struct st_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); } diff --git a/sql/sql_join_cache.h b/sql/sql_join_cache.h index 25b5918ef60..568cc91ecf7 100644 --- a/sql/sql_join_cache.h +++ b/sql/sql_join_cache.h @@ -63,6 +63,7 @@ typedef struct st_cache_field { class JOIN_TAB_SCAN; +struct st_explain_bka_type; /* JOIN_CACHE is the base class to support the implementations of @@ -658,7 +659,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(struct st_explain_bka_type *explain); THD *thd(); @@ -1336,7 +1337,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(struct st_explain_bka_type *explain); }; @@ -1427,5 +1428,5 @@ public: /* 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(struct st_explain_bka_type *explain); }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 6f7edd5623b..74a58bc7d60 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -30,7 +30,7 @@ #include "sp.h" #include "sql_select.h" -static int lex_one_token(void *arg, void *yythd); +static int lex_one_token(void *arg, THD *thd); /* We are using pointer to this variable for distinguishing between assignment @@ -447,6 +447,8 @@ void lex_start(THD *thd) DBUG_ENTER("lex_start"); lex->thd= lex->unit.thd= thd; + + DBUG_ASSERT(!lex->explain); lex->context_stack.empty(); lex->unit.init_query(); @@ -510,7 +512,6 @@ void lex_start(THD *thd) lex->parse_vcol_expr= FALSE; lex->check_exists= FALSE; lex->verbose= 0; - lex->contains_plaintext_password= false; lex->name.str= 0; lex->name.length= 0; @@ -961,9 +962,8 @@ bool consume_comment(Lex_input_stream *lip, int remaining_recursions_permitted) (which can't be followed by a signed number) */ -int MYSQLlex(void *arg, void *yythd) +int MYSQLlex(void *arg, THD *thd) { - THD *thd= (THD *)yythd; Lex_input_stream *lip= & thd->m_parser_state->m_lip; YYSTYPE *yylval=(YYSTYPE*) arg; int token; @@ -982,7 +982,7 @@ int MYSQLlex(void *arg, void *yythd) return token; } - token= lex_one_token(arg, yythd); + token= lex_one_token(arg, thd); switch(token) { case WITH: @@ -993,7 +993,7 @@ int MYSQLlex(void *arg, void *yythd) to transform the grammar into a LALR(1) grammar, which sql_yacc.yy can process. */ - token= lex_one_token(arg, yythd); + token= lex_one_token(arg, thd); switch(token) { case CUBE_SYM: lip->m_digest_psi= MYSQL_ADD_TOKEN(lip->m_digest_psi, WITH_CUBE_SYM, @@ -1022,14 +1022,13 @@ int MYSQLlex(void *arg, void *yythd) return token; } -int lex_one_token(void *arg, void *yythd) +int lex_one_token(void *arg, THD *thd) { reg1 uchar c; bool comment_closed; int tokval, result_state; uint length; enum my_lex_states state; - THD *thd= (THD *)yythd; Lex_input_stream *lip= & thd->m_parser_state->m_lip; LEX *lex= thd->lex; YYSTYPE *yylval=(YYSTYPE*) arg; @@ -2547,7 +2546,8 @@ void Query_tables_list::destroy_query_tables_list() */ LEX::LEX() - :result(0), option_type(OPT_DEFAULT), is_lex_started(0), + : explain(NULL), + result(0), option_type(OPT_DEFAULT), is_lex_started(0), limit_rows_examined_cnt(ULONGLONG_MAX) { @@ -3490,6 +3490,18 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) 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) { /* @@ -3765,10 +3777,7 @@ void SELECT_LEX::mark_as_belong_to_derived(TABLE_LIST *derived) TABLE_LIST *tl; List_iterator<TABLE_LIST> ti(leaf_tables); while ((tl= ti++)) - { - tl->open_type= OT_BASE_ONLY; tl->belong_to_derived= derived; - } } @@ -4063,7 +4072,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); @@ -4168,114 +4178,85 @@ 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 print_explain_message_line(select_result_sink *result, - SELECT_LEX *select_lex, - bool on_the_fly, - uint8 options, - const char *message); - - -int st_select_lex::print_explain(select_result_sink *output, - uint8 explain_flags, - bool *printed_anything) +int LEX::print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything) { int res; - if (join && join->have_query_plan == JOIN::QEP_AVAILABLE) + if (explain && explain->have_query_plan()) { - /* - There is a number of reasons join can be marked as degenerate, so all - three conditions below can happen simultaneously, or individually: - */ - *printed_anything= TRUE; - if (!join->table_count || !join->tables_list || join->zero_result_cause) - { - /* It's a degenerate join */ - const char *cause= join->zero_result_cause ? join-> zero_result_cause : - "No tables used"; - res= join->print_explain(output, explain_flags, TRUE, FALSE, FALSE, - FALSE, cause); - } - else - { - res= join->print_explain(output, explain_flags, TRUE, - join->need_tmp, // need_tmp_table - !join->skip_sort_order && !join->no_order && - (join->order || join->group_list), // bool need_order - join->select_distinct, // bool distinct - NULL); //const char *message - } - if (res) - goto err; - - for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); - unit; - unit= unit->next_unit()) - { - /* - Display subqueries only if they are not parts of eliminated WHERE/ON - clauses. - */ - if (!(unit->item && unit->item->eliminated)) - { - if ((res= unit->print_explain(output, explain_flags, printed_anything))) - goto err; - } - } + res= explain->print_explain(output, explain_flags); + *printed_anything= true; } else { - const char *msg; - if (!join) - DBUG_ASSERT(0); /* Seems not to be possible */ - - /* Not printing anything useful, don't touch *printed_anything here */ - if (join->have_query_plan == JOIN::QEP_NOT_PRESENT_YET) - msg= "Not yet optimized"; - else - { - DBUG_ASSERT(join->have_query_plan == JOIN::QEP_DELETED); - msg= "Query plan already deleted"; - } - res= print_explain_message_line(output, this, TRUE /* on_the_fly */, - 0, msg); + res= 0; + *printed_anything= false; } -err: return res; } -int st_select_lex_unit::print_explain(select_result_sink *output, - uint8 explain_flags, bool *printed_anything) +/* + 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) { - int res= 0; SELECT_LEX *first= first_select(); - - if (first && !first->next_select() && !first->join) - { - /* - If there is only one child, 'first', and it has join==NULL, emit "not in - EXPLAIN state" error. - */ - const char *msg="Query plan already deleted"; - res= print_explain_message_line(output, first, TRUE /* on_the_fly */, - 0, msg); - return 0; - } + Explain_union *eu= new (output->mem_root) Explain_union; for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) - { - if ((res= sl->print_explain(output, explain_flags, printed_anything))) - break; - } + eu->add_select(sl->select_number); + + eu->fake_select_type= "UNION RESULT"; + eu->using_filesort= test(global_parameters->order_list.first); + + // Save the UNION node + output->add_node(eu); - /* Note: fake_select_lex->join may be NULL or non-NULL at this point */ + 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) { - res= print_fake_select_lex_join(output, TRUE /* on the fly */, - fake_select_lex, explain_flags); + 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); + } + } } - return res; + return 0; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 59f7c122646..f892f1bf3c7 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -534,7 +534,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_query(LEX *lex, THD *thd, String *str); class st_select_lex_unit: public st_select_lex_node { protected: @@ -645,8 +650,9 @@ public: friend int subselect_union_engine::exec(); List<Item> *get_unit_column_types(); - int print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything); + + int save_union_explain(Explain_query *output); + int save_union_explain_part2(Explain_query *output); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -943,6 +949,10 @@ 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(bool on_the_fly); @@ -971,8 +981,7 @@ public: bool save_prep_leaf_tables(THD *thd); bool is_merged_child_of(st_select_lex *ancestor); - int print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything); + /* For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags: - Non-aggregated fields are used in this select. @@ -2209,6 +2218,89 @@ protected: LEX *m_lex; }; + +class Delete_plan; +class SQL_SELECT; + +class Explain_query; +class Explain_update; + +/* + 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; } + + void save_explain_data(Explain_query *query); + void save_explain_data_intern(Explain_query *query, Explain_update *eu); + 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 save_explain_data(Explain_query *query); +}; + + /* The state of the lex parsing. This is saved in the THD struct */ struct LEX: public Query_tables_list @@ -2219,6 +2311,9 @@ 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; char *length,*dec,*change; LEX_STRING name; @@ -2333,7 +2428,6 @@ struct LEX: public Query_tables_list this command. */ bool parse_vcol_expr; - bool with_persistent_for_clause; // uses PERSISTENT FOR clause (in ANALYZE) enum SSL_type ssl_type; /* defined in violite.h */ enum enum_duplicates duplicates; @@ -2342,6 +2436,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; @@ -2404,7 +2500,6 @@ 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; - bool contains_plaintext_password; sp_pcontext *spcont; @@ -2637,6 +2732,9 @@ struct LEX: public Query_tables_list } return FALSE; } + + int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); }; @@ -2812,7 +2910,7 @@ extern void lex_start(THD *thd); extern void lex_end(LEX *lex); void end_lex_with_single_table(THD *thd, TABLE *table, LEX *old_lex); int init_lex_with_single_table(THD *thd, TABLE *table, LEX *lex); -extern int MYSQLlex(void *arg, void *yythd); +extern int MYSQLlex(void *arg, THD *thd); extern void trim_whitespace(CHARSET_INFO *cs, LEX_STRING *str); diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 0d0efb0c21f..281d1de7877 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -1,5 +1,6 @@ /* - Copyright (c) 2000, 2011, Oracle and/or its affiliates. + Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2010, 2013, Monty Progrm 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 @@ -362,11 +363,11 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, MY_RETURN_REAL_PATH); } - if (thd->rli_slave) + if (thd->rgi_slave) { #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) - if (strncmp(thd->rli_slave->slave_patternload_file, name, - thd->rli_slave->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 diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3b4c2dee2bb..72f85eb2f94 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -125,8 +125,9 @@ static void wsrep_mysql_parse(THD *thd, char *rawbuf, uint length, "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 *); @@ -406,8 +407,11 @@ 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; /* @todo SQLCOM_CREATE_FUNCTION should have CF_AUTO_COMMIT_TRANS @@ -466,9 +470,13 @@ void init_update_queries(void) 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_CREATE_ROLE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_ROLE]|= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_REVOKE]|= 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_GRANT_ROLE]|= 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; @@ -669,7 +677,8 @@ static void handle_bootstrap_impl(THD *thd) #endif /* EMBEDDED_LIBRARY */ 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 @@ -763,6 +772,7 @@ 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; @@ -913,7 +923,6 @@ bool do_command(THD *thd) net_new_transaction(net); - /* Save for user statistics */ thd->start_bytes_received= thd->status_var.bytes_received; @@ -1065,6 +1074,7 @@ bool do_command(THD *thd) 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)) { @@ -1083,6 +1093,8 @@ bool do_command(THD *thd) thd->wsrep_retry_command = COM_CONNECT; } #endif /* WITH_WSREP */ + DBUG_ASSERT(!thd->apc_target.is_enabled()); + out: /* The statement instrumentation must be closed in all cases. */ DBUG_ASSERT(thd->m_statement_psi == NULL); @@ -1293,6 +1305,14 @@ bool dispatch_command(enum enum_server_command command, THD *thd, if (!(server_command_flags[command] & CF_SKIP_QUESTIONS)) statistic_increment(thd->status_var.questions, &LOCK_status); + /* Copy data for user stats */ + if ((thd->userstat_running= opt_userstat_running)) + { + thd->start_cpu_time= my_getcputime(); + memcpy(&thd->org_status_var, &thd->status_var, sizeof(thd->status_var)); + thd->select_commands= thd->update_commands= thd->other_commands= 0; + } + /** Clear the set of flags that are expected to be cleared at the beginning of each command. @@ -1470,6 +1490,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, 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)) @@ -1576,7 +1597,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; } packet= arg_end + 1; - mysql_reset_thd_for_next_command(thd, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); lex_start(thd); /* Must be before we init the table list. */ if (lower_case_table_names) @@ -1809,7 +1830,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: @@ -1928,10 +1949,15 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } +/* + @note + This function must call delete_explain_query(). +*/ void log_slow_statement(THD *thd) { DBUG_ENTER("log_slow_statement"); + /* The following should never be true with our current code base, but better to keep this here so we don't accidently try to log a @@ -1940,11 +1966,15 @@ void log_slow_statement(THD *thd) if (unlikely(thd->in_sub_stmt)) DBUG_VOID_RETURN; // Don't set time for sub stmt + /* 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))) + { + delete_explain_query(thd->lex); DBUG_VOID_RETURN; + } if (((thd->server_status & SERVER_QUERY_WAS_SLOW) || ((thd->server_status & @@ -1966,6 +1996,8 @@ void log_slow_statement(THD *thd) slow_log_print(thd, thd->query(), thd->query_length(), thd->utime_after_query); } + + delete_explain_query(thd->lex); DBUG_VOID_RETURN; } @@ -2062,8 +2094,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 @@ -2190,7 +2222,6 @@ static void reset_one_shot_variables(THD *thd) } -static bool sp_process_definer(THD *thd) { DBUG_ENTER("sp_process_definer"); @@ -2227,7 +2258,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); @@ -2241,20 +2272,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. */ @@ -2662,7 +2697,7 @@ mysql_execute_command(THD *thd) /* Release metadata locks acquired in this transaction. */ thd->mdl_context.release_transactional_locks(); } - + #ifndef DBUG_OFF if (lex->sql_command != SQLCOM_SET_OPTION) DEBUG_SYNC(thd,"before_execute_sql_command"); @@ -3715,6 +3750,7 @@ end_with_restore_list: case SQLCOM_INSERT_SELECT: { select_result *sel_result; + bool explain= test(lex->describe); DBUG_ASSERT(first_table == all_tables && first_table != 0); if ((res= insert_precheck(thd, all_tables))) break; @@ -3793,6 +3829,10 @@ end_with_restore_list: } delete sel_result; } + + if (!res && explain) + 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; @@ -3811,6 +3851,7 @@ end_with_restore_list: } case SQLCOM_DELETE: { + select_result *sel_result=lex->result; DBUG_ASSERT(first_table == all_tables && first_table != 0); if ((res= delete_precheck(thd, all_tables))) break; @@ -3818,9 +3859,13 @@ end_with_restore_list: 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); + if (!(sel_result= lex->result) && !(sel_result= new select_send())) + return 1; + res = mysql_delete(thd, all_tables, + select_lex->where, &select_lex->order_list, + unit->select_limit_cnt, select_lex->options, + sel_result); + delete sel_result; MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func()); break; } @@ -3828,7 +3873,8 @@ end_with_restore_list: { DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first; - multi_delete *del_result; + bool explain= test(lex->describe); + multi_delete *result; if ((res= multi_delete_precheck(thd, all_tables))) break; @@ -3850,25 +3896,34 @@ 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 multi_delete(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 (explain) + res= thd->lex->explain->send_explain(thd); + } + delete result; + } } else { @@ -4046,8 +4101,7 @@ end_with_restore_list: if (open_temporary_tables(thd, all_tables)) goto error; - if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, - FALSE, UINT_MAX, FALSE)) + if (lock_tables_precheck(thd, all_tables)) goto error; thd->variables.option_bits|= OPTION_TABLE_LOCK; @@ -4286,24 +4340,28 @@ 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) && 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 */ WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL) - if (!(res= mysql_drop_user(thd, lex->users_list))) + if (!(res= mysql_drop_user(thd, lex->users_list, + lex->sql_command == SQLCOM_DROP_ROLE))) my_ok(thd); break; } @@ -4324,9 +4382,6 @@ 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))) @@ -4345,42 +4400,58 @@ 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, 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); /* 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; + else if (user->password.str) + { + // Are we trying to change a password of another user? + const char *hostname= user->host.str, *username=user->user.str; + bool userok; + if (username == current_user.str) + { + username= thd->security_ctx->priv_user; + hostname= thd->security_ctx->priv_host; + userok= true; + } + else + { + if (!hostname) + hostname= host_not_specified.str; + userok= is_acl_user(hostname, username); + } + + if (userok && check_change_password (thd, hostname, username, + user->password.str, + user->password.length)) + goto error; + } } } if (first_table) @@ -4427,9 +4498,9 @@ end_with_restore_list: { 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) { @@ -4448,6 +4519,14 @@ end_with_restore_list: } break; } + case SQLCOM_REVOKE_ROLE: + case SQLCOM_GRANT_ROLE: + { + 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: /* @@ -4515,7 +4594,7 @@ end_with_restore_list: 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)) @@ -4524,7 +4603,7 @@ end_with_restore_list: 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()), @@ -4545,11 +4624,17 @@ end_with_restore_list: #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)) || + + if (grant_user->user.str && + !strcmp(thd->security_ctx->priv_user, grant_user->user.str)) + grant_user->user= current_user; + + if (grant_user->user.str == current_user.str || + grant_user->user.str == current_role.str || + grant_user->user.str == current_user_and_current_role.str || !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 0)) { res = mysql_show_grants(thd, grant_user); @@ -4592,6 +4677,7 @@ end_with_restore_list: 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(); @@ -5413,8 +5499,8 @@ finish: ha_maria::implicit_commit(thd, FALSE); #endif } - lex->unit.cleanup(); + /* Free tables */ THD_STAGE_INFO(thd, stage_closing_tables); close_thread_tables(thd); @@ -5507,24 +5593,37 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) if (!(result= new select_send())) return 1; /* purecov: inspected */ thd->send_explain_fields(result); - res= mysql_explain_union(thd, &thd->lex->unit, result); + /* - 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, &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, Sql_condition::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(); + thd->lex->explain->print_explain(result, thd->lex->describe); + 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(). + */ + thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_YES, str.c_ptr_safe()); + } } + if (res) result->abort_result_set(); else @@ -5544,7 +5643,8 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) /* Count number of empty select queries */ if (!thd->get_sent_row_count()) status_var_increment(thd->status_var.empty_queries); - status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count()); + else + status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count()); #ifdef WITH_WSREP if (lex->sql_command == SQLCOM_SHOW_STATUS) wsrep_free_status(thd); #endif /* WITH_WSREP */ @@ -5733,8 +5833,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 */ @@ -5778,8 +5882,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", @@ -6356,15 +6466,15 @@ bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, ulong *yystacksize) @todo Call it after we use THD for queries, not before. */ -void mysql_reset_thd_for_next_command(THD *thd, my_bool calculate_userstat) +void mysql_reset_thd_for_next_command(THD *thd) { - thd->reset_for_next_command(calculate_userstat); + thd->reset_for_next_command(); } -void THD::reset_for_next_command(bool calculate_userstat) +void THD::reset_for_next_command() { THD *thd= this; - DBUG_ENTER("mysql_reset_thd_for_next_command"); + DBUG_ENTER("THD::reset_for_next_command"); DBUG_ASSERT(!thd->spcont); /* not for substatements of routines */ DBUG_ASSERT(! thd->in_sub_stmt); thd->free_list= 0; @@ -6429,14 +6539,6 @@ void THD::reset_for_next_command(bool calculate_userstat) thd->m_sent_row_count= thd->m_examined_row_count= 0; thd->accessed_rows_and_keys= 0; - /* Copy data for user stats */ - if ((thd->userstat_running= calculate_userstat)) - { - thd->start_cpu_time= my_getcputime(); - memcpy(&thd->org_status_var, &thd->status_var, sizeof(thd->status_var)); - thd->select_commands= thd->update_commands= thd->other_commands= 0; - } - thd->query_plan_flags= QPLAN_INIT; thd->query_plan_fsort_passes= 0; @@ -6646,7 +6748,7 @@ static void wsrep_mysql_parse(THD *thd, char *rawbuf, uint length, if (thd->wsrep_conflict_state == ABORTED || thd->wsrep_conflict_state == CERT_FAILURE) { - mysql_reset_thd_for_next_command(thd, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); thd->killed= NOT_KILLED; if (is_autocommit && thd->lex->sql_command != SQLCOM_SELECT && @@ -6743,7 +6845,7 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, FIXME: cleanup the dependencies in the code to simplify this. */ lex_start(thd); - mysql_reset_thd_for_next_command(thd, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); if (query_cache_send_result_to_client(thd, rawbuf, length) <= 0) { @@ -6830,6 +6932,8 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, 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(); } DBUG_VOID_RETURN; } @@ -6856,7 +6960,7 @@ 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, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); if (!parse_sql(thd, & parser_state, NULL, true) && all_tables_not_ok(thd, lex->select_lex.table_list.first)) @@ -7651,12 +7755,13 @@ void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List<String> *using_fields, Find a thread by id and return it, locking it LOCK_thd_data @param id Identifier of the thread we're looking for + @param query_id If true, search by query_id instead of thread_id @return NULL - not found pointer - thread found, and its LOCK_thd_data is locked. */ -THD *find_thread_by_id(ulong id) +THD *find_thread_by_id(longlong id, bool query_id) { THD *tmp; mysql_mutex_lock(&LOCK_thread_count); // For unlink from list @@ -7665,7 +7770,7 @@ THD *find_thread_by_id(ulong id) { 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; @@ -7677,24 +7782,26 @@ THD *find_thread_by_id(ulong id) /** - kill on thread. + kill one thread. @param thd Thread class - @param id Thread id - @param only_kill_query Should it kill the query or the connection + @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, ulong id, killed_state kill_signal) +uint +kill_one_thread(THD *thd, longlong id, killed_state kill_signal, killed_type type) { THD *tmp; - uint error=ER_NO_SUCH_THREAD; + uint error= (type == KILL_TYPE_QUERY ? ER_NO_SUCH_QUERY : ER_NO_SUCH_THREAD); DBUG_ENTER("kill_one_thread"); - DBUG_PRINT("enter", ("id: %lu signal: %u", id, (uint) kill_signal)); + DBUG_PRINT("enter", ("id: %lld signal: %u", id, (uint) kill_signal)); - if ((tmp= find_thread_by_id(id))) + if (id && (tmp= find_thread_by_id(id, type == KILL_TYPE_QUERY))) { /* If we're SUPER, we can KILL anything, including system-threads. @@ -7818,21 +7925,20 @@ 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)) my_ok(thd); @@ -8438,6 +8544,35 @@ err: /** + 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 @@ -8478,16 +8613,23 @@ 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; @@ -8505,16 +8647,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; } @@ -8549,27 +8697,6 @@ LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name) } /** - 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 @@ -8753,7 +8880,7 @@ bool check_host_name(LEX_STRING *str) } -extern int MYSQLparse(void *thd); // from sql_yacc.cc +extern int MYSQLparse(THD *thd); // from sql_yacc.cc /** diff --git a/sql/sql_parse.h b/sql/sql_parse.h index 9ecdd5b53bb..c2a7a184c20 100644 --- a/sql/sql_parse.h +++ b/sql/sql_parse.h @@ -62,10 +62,11 @@ 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); +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, const char *err_msg, uint max_byte_length); bool check_string_char_length(LEX_STRING *str, const char *err_msg, @@ -83,7 +84,7 @@ 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, my_bool calculate_userstat); +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); diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 9e4c48b47ff..87bfcfcdfc2 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -177,7 +177,8 @@ int get_part_iter_for_interval_via_walking(partition_info *part_info, static int cmp_rec_and_tuple(part_column_list_val *val, uint32 nvals_in_rec); static int cmp_rec_and_tuple_prune(part_column_list_val *val, uint32 n_vals_in_rec, - bool tail_is_min); + bool is_left_endpoint, + bool include_endpoint); /* Convert constants in VALUES definition to the character set the @@ -3102,44 +3103,6 @@ notfound: } -/* - Find the sub-array part_info->list_array that corresponds to given interval - - SYNOPSIS - get_list_array_idx_for_endpoint() - part_info Partitioning info (partitioning type must be LIST) - left_endpoint TRUE - the interval is [a; +inf) or (a; +inf) - FALSE - the interval is (-inf; a] or (-inf; a) - include_endpoint TRUE iff the interval includes the endpoint - - DESCRIPTION - This function finds the sub-array of part_info->list_array where values of - list_array[idx].list_value are contained within the specifed interval. - list_array is ordered by list_value, so - 1. For [a; +inf) or (a; +inf)-type intervals (left_endpoint==TRUE), the - sought sub-array starts at some index idx and continues till array end. - The function returns first number idx, such that - list_array[idx].list_value is contained within the passed interval. - - 2. For (-inf; a] or (-inf; a)-type intervals (left_endpoint==FALSE), the - sought sub-array starts at array start and continues till some last - index idx. - The function returns first number idx, such that - list_array[idx].list_value is NOT contained within the passed interval. - If all array elements are contained, part_info->num_list_values is - returned. - - NOTE - The caller will call this function and then will run along the sub-array of - list_array to collect partition ids. If the number of list values is - significantly higher then number of partitions, this could be slow and - we could invent some other approach. The "run over list array" part is - already wrapped in a get_next()-like function. - - RETURN - The edge of corresponding sub-array of part_info->list_array -*/ - uint32 get_partition_id_cols_list_for_endpoint(partition_info *part_info, bool left_endpoint, bool include_endpoint, @@ -3147,37 +3110,81 @@ uint32 get_partition_id_cols_list_for_endpoint(partition_info *part_info, { part_column_list_val *list_col_array= part_info->list_col_array; uint num_columns= part_info->part_field_list.elements; - int list_index, cmp; + uint list_index; uint min_list_index= 0; - uint max_list_index= part_info->num_list_values - 1; - bool tailf= !(left_endpoint ^ include_endpoint); + uint max_list_index= part_info->num_list_values; DBUG_ENTER("get_partition_id_cols_list_for_endpoint"); + /* Find the matching partition (including taking endpoint into account). */ do { + /* Midpoint, adjusted down, so it can never be > last index. */ list_index= (max_list_index + min_list_index) >> 1; - cmp= cmp_rec_and_tuple_prune(list_col_array + list_index*num_columns, - nparts, tailf); - if (cmp > 0) + if (cmp_rec_and_tuple_prune(list_col_array + list_index*num_columns, + nparts, left_endpoint, include_endpoint) > 0) min_list_index= list_index + 1; - else if (cmp < 0) - { - if (!list_index) - goto notfound; - max_list_index= list_index - 1; - } - else - { - DBUG_RETURN(list_index + test(!tailf)); - } - } while (max_list_index >= min_list_index); - if (cmp > 0) - list_index++; -notfound: + else + max_list_index= list_index; + } while (max_list_index > min_list_index); + list_index= max_list_index; + + /* Given value must be LESS THAN or EQUAL to the found partition. */ + DBUG_ASSERT(list_index == part_info->num_list_values || + (0 >= cmp_rec_and_tuple_prune(list_col_array + + list_index*num_columns, + nparts, left_endpoint, + include_endpoint))); + /* Given value must be GREATER THAN the previous partition. */ + DBUG_ASSERT(list_index == 0 || + (0 < cmp_rec_and_tuple_prune(list_col_array + + (list_index - 1)*num_columns, + nparts, left_endpoint, + include_endpoint))); + + if (!left_endpoint) + { + /* Set the end after this list tuple if not already after the last. */ + if (list_index < part_info->num_parts) + list_index++; + } + DBUG_RETURN(list_index); } +/** + Find the sub-array part_info->list_array that corresponds to given interval. + + @param part_info Partitioning info (partitioning type must be LIST) + @param left_endpoint TRUE - the interval is [a; +inf) or (a; +inf) + FALSE - the interval is (-inf; a] or (-inf; a) + @param include_endpoint TRUE iff the interval includes the endpoint + + This function finds the sub-array of part_info->list_array where values of + list_array[idx].list_value are contained within the specifed interval. + list_array is ordered by list_value, so + 1. For [a; +inf) or (a; +inf)-type intervals (left_endpoint==TRUE), the + sought sub-array starts at some index idx and continues till array end. + The function returns first number idx, such that + list_array[idx].list_value is contained within the passed interval. + + 2. For (-inf; a] or (-inf; a)-type intervals (left_endpoint==FALSE), the + sought sub-array starts at array start and continues till some last + index idx. + The function returns first number idx, such that + list_array[idx].list_value is NOT contained within the passed interval. + If all array elements are contained, part_info->num_list_values is + returned. + + @note The caller will call this function and then will run along the + sub-array of list_array to collect partition ids. If the number of list + values is significantly higher then number of partitions, this could be slow + and we could invent some other approach. The "run over list array" part is + already wrapped in a get_next()-like function. + + @return The index of corresponding sub-array of part_info->list_array. +*/ + uint32 get_list_array_idx_for_endpoint_charset(partition_info *part_info, bool left_endpoint, bool include_endpoint) @@ -7427,15 +7434,17 @@ uint32 store_tuple_to_record(Field **pfield, return nparts; } -/* - RANGE(columns) partitioning: compare value bound and probe tuple. +/** + RANGE(columns) partitioning: compare partition value bound and probe tuple. - The value bound always is a full tuple (but may include the MAXVALUE - special value). + @param val Partition column values. + @param nvals_in_rec Number of (prefix) fields to compare. - The probe tuple may be a prefix of partitioning tuple. The tail_is_min - parameter specifies whether the suffix components should be assumed to - hold MAXVALUE + @return Less than/Equal to/Greater than 0 if the record is L/E/G than val. + + @note The partition value bound is always a full tuple (but may include the + MAXVALUE special value). The probe tuple may be a prefix of partitioning + tuple. */ static int cmp_rec_and_tuple(part_column_list_val *val, uint32 nvals_in_rec) @@ -7465,25 +7474,73 @@ static int cmp_rec_and_tuple(part_column_list_val *val, uint32 nvals_in_rec) } +/** + Compare record and columns partition tuple including endpoint handling. + + @param val Columns partition tuple + @param n_vals_in_rec Number of columns to compare + @param is_left_endpoint True if left endpoint (part_tuple < rec or + part_tuple <= rec) + @param include_endpoint If endpoint is included (part_tuple <= rec or + rec <= part_tuple) + + @return Less than/Equal to/Greater than 0 if the record is L/E/G than + the partition tuple. + + @see get_list_array_idx_for_endpoint() and + get_partition_id_range_for_endpoint(). +*/ + static int cmp_rec_and_tuple_prune(part_column_list_val *val, uint32 n_vals_in_rec, - bool tail_is_min) + bool is_left_endpoint, + bool include_endpoint) { int cmp; Field **field; - partition_info *part_info; if ((cmp= cmp_rec_and_tuple(val, n_vals_in_rec))) return cmp; - part_info= val->part_info; - field= part_info->part_field_array + n_vals_in_rec; - for (; *field; field++, val++) + field= val->part_info->part_field_array + n_vals_in_rec; + if (!(*field)) { - if (tail_is_min) - return -1; - if (!tail_is_min && !val->max_value) - return +1; + /* + Full match, if right endpoint and not including the endpoint, + (rec < part) return lesser. + */ + if (!is_left_endpoint && !include_endpoint) + return -4; + + /* Otherwise they are equal! */ + return 0; } - return 0; + /* + The prefix is equal and there are more partition columns to compare. + + If including left endpoint or not including right endpoint + then the record is considered lesser compared to the partition. + + i.e: + part(10, x) <= rec(10, unknown) and rec(10, unknown) < part(10, x) + part <= rec -> lesser (i.e. this or previous partitions) + rec < part -> lesser (i.e. this or previous partitions) + */ + if (is_left_endpoint == include_endpoint) + return -2; + + /* + If right endpoint and the first additional partition value + is MAXVALUE, then the record is lesser. + */ + if (!is_left_endpoint && (val + n_vals_in_rec)->max_value) + return -3; + + /* + Otherwise the record is considered greater. + + rec <= part -> greater (i.e. does not match this partition, seek higher). + part < rec -> greater (i.e. does not match this partition, seek higher). + */ + return 2; } @@ -7494,91 +7551,65 @@ typedef uint32 (*get_col_endpoint_func)(partition_info*, bool left_endpoint, bool include_endpoint, uint32 num_parts); -/* - Partitioning Interval Analysis: Initialize the iterator for "mapping" case +/** + Get partition for RANGE COLUMNS endpoint. - SYNOPSIS - get_part_iter_for_interval_via_mapping() - part_info Partition info - is_subpart TRUE - act for subpartitioning - FALSE - act for partitioning - min_value minimum field value, in opt_range key format. - max_value minimum field value, in opt_range key format. - flags Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE, - NO_MAX_RANGE. - part_iter Iterator structure to be initialized + @param part_info Partitioning metadata. + @param is_left_endpoint True if left endpoint (const <=/< cols) + @param include_endpoint True if range includes the endpoint (<=/>=) + @param nparts Total number of partitions - DESCRIPTION - Initialize partition set iterator to walk over the interval in - ordered-array-of-partitions (for RANGE partitioning) or - ordered-array-of-list-constants (for LIST partitioning) space. + @return Partition id of matching partition. - IMPLEMENTATION - This function is used when partitioning is done by - <RANGE|LIST>(ascending_func(t.field)), and we can map an interval in - t.field space into a sub-array of partition_info::range_int_array or - partition_info::list_array (see get_partition_id_range_for_endpoint, - get_list_array_idx_for_endpoint for details). - - The function performs this interval mapping, and sets the iterator to - traverse the sub-array and return appropriate partitions. - - RETURN - 0 - No matching partitions (iterator not initialized) - 1 - Ok, iterator intialized for traversal of matching partitions. - -1 - All partitions would match (iterator not initialized) + @see get_partition_id_cols_list_for_endpoint and + get_partition_id_range_for_endpoint. */ uint32 get_partition_id_cols_range_for_endpoint(partition_info *part_info, - bool left_endpoint, + bool is_left_endpoint, bool include_endpoint, uint32 nparts) { - uint max_partition= part_info->num_parts - 1; - uint min_part_id= 0, max_part_id= max_partition, loc_part_id; + uint min_part_id= 0, max_part_id= part_info->num_parts, loc_part_id; part_column_list_val *range_col_array= part_info->range_col_array; uint num_columns= part_info->part_field_list.elements; - bool tailf= !(left_endpoint ^ include_endpoint); DBUG_ENTER("get_partition_id_cols_range_for_endpoint"); - /* Get the partitioning function value for the endpoint */ - while (max_part_id > min_part_id) + /* Find the matching partition (including taking endpoint into account). */ + do { - loc_part_id= (max_part_id + min_part_id + 1) >> 1; - if (cmp_rec_and_tuple_prune(range_col_array + loc_part_id*num_columns, - nparts, tailf) >= 0) + /* Midpoint, adjusted down, so it can never be > last partition. */ + loc_part_id= (max_part_id + min_part_id) >> 1; + if (0 <= cmp_rec_and_tuple_prune(range_col_array + + loc_part_id * num_columns, + nparts, + is_left_endpoint, + include_endpoint)) min_part_id= loc_part_id + 1; else - max_part_id= loc_part_id - 1; - } + max_part_id= loc_part_id; + } while (max_part_id > min_part_id); loc_part_id= max_part_id; - if (loc_part_id < max_partition && - cmp_rec_and_tuple_prune(range_col_array + (loc_part_id+1)*num_columns, - nparts, tailf) >= 0 - ) - { - loc_part_id++; - } - if (left_endpoint) - { - if (cmp_rec_and_tuple_prune(range_col_array + loc_part_id*num_columns, - nparts, tailf) >= 0) + + /* Given value must be LESS THAN the found partition. */ + DBUG_ASSERT(loc_part_id == part_info->num_parts || + (0 > cmp_rec_and_tuple_prune(range_col_array + + loc_part_id * num_columns, + nparts, is_left_endpoint, + include_endpoint))); + /* Given value must be GREATER THAN or EQUAL to the previous partition. */ + DBUG_ASSERT(loc_part_id == 0 || + (0 <= cmp_rec_and_tuple_prune(range_col_array + + (loc_part_id - 1) * num_columns, + nparts, is_left_endpoint, + include_endpoint))); + + if (!is_left_endpoint) + { + /* Set the end after this partition if not already after the last. */ + if (loc_part_id < part_info->num_parts) loc_part_id++; } - else - { - if (loc_part_id < max_partition) - { - int res= cmp_rec_and_tuple_prune(range_col_array + - loc_part_id * num_columns, - nparts, tailf); - if (!res) - loc_part_id += test(include_endpoint); - else if (res > 0) - loc_part_id++; - } - loc_part_id++; - } DBUG_RETURN(loc_part_id); } @@ -7650,6 +7681,40 @@ int get_part_iter_for_interval_cols_via_map(partition_info *part_info, } +/** + Partitioning Interval Analysis: Initialize the iterator for "mapping" case + + @param part_info Partition info + @param is_subpart TRUE - act for subpartitioning + FALSE - act for partitioning + @param store_length_array Ignored. + @param min_value minimum field value, in opt_range key format. + @param max_value minimum field value, in opt_range key format. + @param min_len Ignored. + @param max_len Ignored. + @param flags Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE, + NO_MAX_RANGE. + @param part_iter Iterator structure to be initialized + + @details Initialize partition set iterator to walk over the interval in + ordered-array-of-partitions (for RANGE partitioning) or + ordered-array-of-list-constants (for LIST partitioning) space. + + This function is used when partitioning is done by + <RANGE|LIST>(ascending_func(t.field)), and we can map an interval in + t.field space into a sub-array of partition_info::range_int_array or + partition_info::list_array (see get_partition_id_range_for_endpoint, + get_list_array_idx_for_endpoint for details). + + The function performs this interval mapping, and sets the iterator to + traverse the sub-array and return appropriate partitions. + + @return Status of iterator + @retval 0 No matching partitions (iterator not initialized) + @retval 1 Ok, iterator intialized for traversal of matching partitions. + @retval -1 All partitions would match (iterator not initialized) +*/ + int get_part_iter_for_interval_via_mapping(partition_info *part_info, bool is_subpart, uint32 *store_length_array, /* ignored */ diff --git a/sql/sql_plist.h b/sql/sql_plist.h index 8e8c7fcaefb..df50cccc874 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -75,6 +75,11 @@ class I_P_List : public C, public I */ public: I_P_List() : I(&m_first), m_first(NULL) {}; + /* + empty() is used in many places in the code instead of a constructor, to + initialize a bzero-ed I_P_List instance. + */ + inline void empty() { m_first= NULL; C::reset(); I::set_last(&m_first); } inline bool is_empty() const { return (m_first == NULL); } inline void push_front(T* a) diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 5823e21db42..43abcd13fce 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -176,8 +176,7 @@ static struct /* we disable few other plugins by default */ { "ndbcluster", PLUGIN_OFF }, - { "feedback", PLUGIN_OFF }, - { "pbxt", PLUGIN_OFF } + { "feedback", PLUGIN_OFF } }; /* support for Services */ @@ -516,7 +515,7 @@ 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)) { - 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); } @@ -637,7 +636,7 @@ 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)) { - 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); } @@ -691,6 +690,8 @@ static my_bool read_maria_plugin_info(struct st_plugin_dl *plugin_dl, sym= cur; plugin_dl->allocated= true; } + else + sym= ptr; } plugin_dl->plugins= (struct st_maria_plugin *)sym; @@ -779,7 +780,7 @@ static st_plugin_dl *plugin_dl_add(const LEX_STRING *dl, int report) my_snprintf(buf, sizeof(buf), "service '%s' interface version mismatch", list_of_services[i].name); - report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, 0, buf); + report_error(report, ER_CANT_OPEN_LIBRARY, dlpath, ENOEXEC, buf); goto ret; } *(void**)sym= list_of_services[i].service; @@ -1072,7 +1073,7 @@ static bool plugin_add(MEM_ROOT *tmp_root, plugin_type_names[plugin->type].str, " plugin ", tmp.name.str, " not supported by this version of the server", NullS); - report_error(report, ER_CANT_OPEN_LIBRARY, dl->str, 0, buf); + report_error(report, ER_CANT_OPEN_LIBRARY, dl->str, ENOEXEC, buf); goto err; } if (plugin_maturity_map[plugin->maturity] < plugin_maturity) @@ -1084,7 +1085,7 @@ static bool plugin_add(MEM_ROOT *tmp_root, " is prohibited by --plugin-maturity=", plugin_maturity_names[plugin_maturity], NullS); - report_error(report, ER_CANT_OPEN_LIBRARY, dl->str, 0, buf); + report_error(report, ER_CANT_OPEN_LIBRARY, dl->str, EPERM, buf); goto err; } tmp.plugin= plugin; @@ -2437,6 +2438,7 @@ typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_longlong_t, longlong); typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_uint_t, uint); typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_ulong_t, ulong); typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_ulonglong_t, ulonglong); +typedef DECLARE_MYSQL_SYSVAR_SIMPLE(sysvar_double_t, double); typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_int_t, int); typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_long_t, long); @@ -2444,6 +2446,7 @@ typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_longlong_t, longlong); typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_uint_t, uint); typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_ulong_t, ulong); typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_ulonglong_t, ulonglong); +typedef DECLARE_MYSQL_THDVAR_SIMPLE(thdvar_double_t, double); /**************************************************************************** @@ -2659,6 +2662,20 @@ err: return 1; } +static int check_func_double(THD *thd, struct st_mysql_sys_var *var, + void *save, st_mysql_value *value) +{ + double v; + my_bool fixed; + struct my_option option; + + value->val_real(value, &v); + plugin_opt_set_limits(&option, var); + *(double *) save= getopt_double_limit_value(v, &option, &fixed); + + return throw_bounds_warning(thd, var->name, fixed, v); +} + static void update_func_bool(THD *thd, struct st_mysql_sys_var *var, void *tgt, const void *save) @@ -2697,12 +2714,19 @@ static void update_func_str(THD *thd, struct st_mysql_sys_var *var, char *old= *(char**) tgt; if (value) *(char**) tgt= my_strdup(value, MYF(0)); + else + *(char**) tgt= 0; my_free(old); } else *(char**) tgt= value; } +static void update_func_double(THD *thd, struct st_mysql_sys_var *var, + void *tgt, const void *save) +{ + *(double *) tgt= *(double *) save; +} /**************************************************************************** System Variables support @@ -2817,6 +2841,9 @@ static st_bookmark *register_var(const char *plugin, const char *name, case PLUGIN_VAR_STR: size= sizeof(char*); break; + case PLUGIN_VAR_DOUBLE: + size= sizeof(double); + break; default: DBUG_ASSERT(0); return NULL; @@ -3029,6 +3056,11 @@ static char **mysql_sys_var_str(THD* thd, int offset) return (char **) intern_sys_var_ptr(thd, offset, true); } +static double *mysql_sys_var_double(THD* thd, int offset) +{ + return (double *) intern_sys_var_ptr(thd, offset, true); +} + void plugin_thdvar_init(THD *thd) { plugin_ref old_table_plugin= thd->variables.table_plugin; @@ -3187,6 +3219,8 @@ static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var) case PLUGIN_VAR_ENUM: case PLUGIN_VAR_SET: return SHOW_CHAR; + case PLUGIN_VAR_DOUBLE: + return SHOW_DOUBLE; default: DBUG_ASSERT(0); return SHOW_UNDEF; @@ -3207,6 +3241,8 @@ bool sys_var_pluginvar::check_update_type(Item_result type) 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; } @@ -3325,6 +3361,9 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) case PLUGIN_VAR_STR: src= &((sysvar_str_t*) plugin_var)->def_val; break; + case PLUGIN_VAR_DOUBLE: + src= &((sysvar_double_t*) plugin_var)->def_val; + break; case PLUGIN_VAR_INT | PLUGIN_VAR_THDLOCAL: src= &((thdvar_uint_t*) plugin_var)->def_val; break; @@ -3346,6 +3385,9 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) case PLUGIN_VAR_STR | PLUGIN_VAR_THDLOCAL: src= &((thdvar_str_t*) plugin_var)->def_val; break; + case PLUGIN_VAR_DOUBLE | PLUGIN_VAR_THDLOCAL: + src= &((thdvar_double_t*) plugin_var)->def_val; + break; default: DBUG_ASSERT(0); } @@ -3363,6 +3405,13 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) options->max_value= (opt)->max_val; \ options->block_size= (long) (opt)->blk_sz +#define OPTION_SET_LIMITS_DOUBLE(options, opt) \ + options->var_type= GET_DOUBLE; \ + options->def_value= (longlong) getopt_double2ulonglong((opt)->def_val); \ + options->min_value= (longlong) getopt_double2ulonglong((opt)->min_val); \ + options->max_value= getopt_double2ulonglong((opt)->max_val); \ + options->block_size= (long) (opt)->blk_sz; + void plugin_opt_set_limits(struct my_option *options, const struct st_mysql_sys_var *opt) @@ -3413,6 +3462,9 @@ void plugin_opt_set_limits(struct my_option *options, GET_STR_ALLOC : GET_STR); options->def_value= (intptr) ((sysvar_str_t*) opt)->def_val; break; + case PLUGIN_VAR_DOUBLE: + OPTION_SET_LIMITS_DOUBLE(options, (sysvar_double_t*) opt); + break; /* threadlocal variables */ case PLUGIN_VAR_INT | PLUGIN_VAR_THDLOCAL: OPTION_SET_LIMITS(GET_INT, options, (thdvar_int_t*) opt); @@ -3432,6 +3484,9 @@ void plugin_opt_set_limits(struct my_option *options, case PLUGIN_VAR_LONGLONG | PLUGIN_VAR_UNSIGNED | PLUGIN_VAR_THDLOCAL: OPTION_SET_LIMITS(GET_ULL, options, (thdvar_ulonglong_t*) opt); break; + case PLUGIN_VAR_DOUBLE | PLUGIN_VAR_THDLOCAL: + OPTION_SET_LIMITS_DOUBLE(options, (thdvar_double_t*) opt); + break; case PLUGIN_VAR_ENUM | PLUGIN_VAR_THDLOCAL: options->var_type= GET_ENUM; options->typelib= ((thdvar_enum_t*) opt)->typelib; @@ -3583,6 +3638,9 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, case PLUGIN_VAR_SET: ((thdvar_set_t *) opt)->resolve= mysql_sys_var_ulonglong; break; + case PLUGIN_VAR_DOUBLE: + ((thdvar_double_t *) opt)->resolve= mysql_sys_var_double; + break; default: sql_print_error("Unknown variable type code 0x%x in plugin '%s'.", opt->flags, plugin_name); @@ -3646,6 +3704,12 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, if (!opt->update) opt->update= update_func_longlong; break; + case PLUGIN_VAR_DOUBLE: + if (!opt->check) + opt->check= check_func_double; + if (!opt->update) + opt->update= update_func_double; + break; default: sql_print_error("Unknown variable type code 0x%x in plugin '%s'.", opt->flags, plugin_name); diff --git a/sql/sql_plugin_services.h b/sql/sql_plugin_services.h index 6b70048345a..362252eee8a 100644 --- a/sql/sql_plugin_services.h +++ b/sql/sql_plugin_services.h @@ -64,6 +64,16 @@ static struct my_sha1_service_st my_sha1_handler = { my_sha1_multi }; +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 }, @@ -73,6 +83,7 @@ static struct st_service_ref list_of_services[]= { "debug_sync_service", VERSION_debug_sync, 0 }, // updated in plugin_init() { "thd_kill_statement_service", VERSION_kill_statement, &thd_kill_statement_handler }, { "thd_timezone_service", VERSION_thd_timezone, &thd_timezone_handler }, - { "my_sha1_service", VERSION_my_sha1, &my_sha1_handler} + { "my_sha1_service", VERSION_my_sha1, &my_sha1_handler}, + { "logger_service", VERSION_logger, &logger_service_handler }, }; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index da3226afddd..f31438f75c2 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1471,7 +1471,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); } @@ -2305,7 +2308,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, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); if (! (stmt= new Prepared_statement(thd))) goto end; /* out of memory: error is set in Sql_alloc */ @@ -2520,6 +2523,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) { @@ -2580,7 +2584,13 @@ void reinit_stmt_before_use(THD *thd, LEX *lex) /* Fix ORDER list */ for (order= sl->order_list.first; order; order= order->next) order->item= &order->item_ptr; - sl->handle_derived(lex, DT_REINIT); + { +#ifndef DBUG_OFF + bool res= +#endif + sl->handle_derived(lex, DT_REINIT); + DBUG_ASSERT(res == 0); + } } { SELECT_LEX_UNIT *unit= sl->master_unit(); @@ -2689,7 +2699,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, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); if (!(stmt= find_prepared_statement(thd, stmt_id))) { @@ -2788,7 +2798,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, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); status_var_increment(thd->status_var.com_stmt_fetch); if (!(stmt= find_prepared_statement(thd, stmt_id))) @@ -2848,7 +2858,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, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); status_var_increment(thd->status_var.com_stmt_reset); if (!(stmt= find_prepared_statement(thd, stmt_id))) @@ -3963,6 +3973,12 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) thd->m_statement_psi= parent_locker; MYSQL_QUERY_EXEC_DONE(error); } + else + { + thd->lex->sql_command= SQLCOM_SELECT; + status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]); + thd->update_stats(); + } } /* @@ -3981,6 +3997,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; diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index 3bb2164866f..a229c861116 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -333,7 +333,7 @@ bool reload_acl_and_cache(THD *thd, unsigned long long options, { DBUG_ASSERT(thd); tmp_write_to_binlog= 0; - if (reset_master(thd)) + if (reset_master(thd, NULL, 0)) { /* NOTE: my_error() has been already called by reset_master(). */ result= 1; diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index 78acb4a519f..6babdd7c636 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -1,5 +1,6 @@ /* - Copyright (c) 2000, 2010, Oracle and/or its affiliates. + Copyright (c) 2000, 2013, Oracle and/or its affiliates. + 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 @@ -296,7 +297,7 @@ do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name, 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, 0); + ren_table->db, old_alias, NO_FK_CHECKS); } } } diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 412b941e946..28d1c72edf4 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -884,6 +884,28 @@ contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) } +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 errorneous 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. @@ -894,43 +916,51 @@ contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) static int check_slave_start_position(THD *thd, slave_connection_state *st, const char **errormsg, rpl_gtid *error_gtid, - slave_connection_state *until_gtid_state, - HASH *fake_gtid_hash) + slave_connection_state *until_gtid_state) { uint32 i; int err; - rpl_gtid **delete_list= NULL; + slave_connection_state::entry **delete_list= NULL; uint32 delete_idx= 0; - bool slave_state_loaded= false; + + if (rpl_load_gtid_slave_state(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) { - rpl_gtid *slave_gtid= (rpl_gtid *)my_hash_element(&st->hash, 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) - continue; - - if (!slave_state_loaded) { - if (rpl_load_gtid_slave_state(thd)) - { - *errormsg= "Failed to load replication slave GTID state"; - err= ER_CANNOT_LOAD_SLAVE_GTID_STATE; - goto end; - } - slave_state_loaded= true; + /* + 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 (!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 (!start_at_own_slave_pos) { rpl_gtid domain_gtid; rpl_gtid *until_gtid; @@ -942,7 +972,12 @@ check_slave_start_position(THD *thd, slave_connection_state *st, 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; } @@ -966,9 +1001,8 @@ check_slave_start_position(THD *thd, slave_connection_state *st, continue; } - *errormsg= "Requested slave GTID state not found in binlog"; *error_gtid= *slave_gtid; - err= ER_GTID_POSITION_NOT_FOUND_IN_BINLOG; + give_error_start_pos_missing_in_binlog(&err, errormsg, error_gtid); goto end; } @@ -999,7 +1033,6 @@ check_slave_start_position(THD *thd, slave_connection_state *st, &start_gtid) && start_gtid.seq_no > slave_gtid->seq_no) { - rpl_gtid *fake_gtid; /* Start replication within this domain at the first GTID that we logged ourselves after becoming a master. @@ -1009,20 +1042,7 @@ check_slave_start_position(THD *thd, slave_connection_state *st, --gtid-strict-mode. */ slave_gtid->server_id= global_system_variables.server_id; - if (!(fake_gtid= (rpl_gtid *)my_malloc(sizeof(*fake_gtid), MYF(0)))) - { - *errormsg= "Out of memory while checking slave start position"; - err= ER_OUT_OF_RESOURCES; - goto end; - } - *fake_gtid= *slave_gtid; - if (my_hash_insert(fake_gtid_hash, (uchar *)fake_gtid)) - { - my_free(fake_gtid); - *errormsg= "Out of memory while checking slave start position"; - err= ER_OUT_OF_RESOURCES; - goto end; - } + 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)) @@ -1042,7 +1062,7 @@ check_slave_start_position(THD *thd, slave_connection_state *st, */ if (!delete_list) { - if (!(delete_list= (rpl_gtid **) + 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"; @@ -1050,7 +1070,7 @@ check_slave_start_position(THD *thd, slave_connection_state *st, goto end; } } - delete_list[delete_idx++]= slave_gtid; + delete_list[delete_idx++]= slave_gtid_entry; } } @@ -1058,7 +1078,7 @@ check_slave_start_position(THD *thd, slave_connection_state *st, if (delete_list) { for (i= 0; i < delete_idx; ++i) - st->remove(delete_list[i]); + st->remove(&(delete_list[i]->gtid)); } err= 0; @@ -1249,6 +1269,7 @@ gtid_state_from_pos(const char *name, uint32 offset, uint8 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)) { @@ -1260,6 +1281,13 @@ gtid_state_from_pos(const char *name, uint32 offset, 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. @@ -1295,6 +1323,8 @@ gtid_state_from_pos(const char *name, uint32 offset, 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 " @@ -1304,6 +1334,15 @@ gtid_state_from_pos(const char *name, uint32 offset, 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 != FORMAT_DESCRIPTION_EVENT && !found_format_description_event) { @@ -1328,7 +1367,7 @@ gtid_state_from_pos(const char *name, uint32 offset, } status= Gtid_list_log_event::peek(packet.ptr(), packet.length(), current_checksum_alg, - >id_list, &list_len); + >id_list, &list_len, fdev); if (status) { errormsg= "Error reading Gtid_list_log_event while searching " @@ -1356,7 +1395,7 @@ gtid_state_from_pos(const char *name, uint32 offset, uchar flags2; if (Gtid_log_event::peek(packet.ptr(), packet.length(), current_checksum_alg, >id.domain_id, - >id.server_id, >id.seq_no, &flags2)) + >id.server_id, >id.seq_no, &flags2, fdev)) { errormsg= "Corrupt gtid_log_event found while scanning binlog to find " "initial slave position"; @@ -1379,6 +1418,7 @@ gtid_state_from_pos(const char *name, uint32 offset, } end: + delete fdev; end_io_cache(&cache); mysql_file_close(file, MYF(MY_WME)); @@ -1482,7 +1522,8 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, enum_gtid_until_state *gtid_until_group, rpl_binlog_state *until_binlog_state, bool slave_gtid_strict_mode, rpl_gtid *error_gtid, - bool *send_fake_gtid_list, HASH *fake_gtid_hash) + bool *send_fake_gtid_list, + Format_description_log_event *fdev) { my_off_t pos; size_t len= packet->length(); @@ -1496,7 +1537,7 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, if (ev_offset > len || Gtid_list_log_event::peek(packet->ptr()+ev_offset, len - ev_offset, current_checksum_alg, - >id_list, &list_len)) + >id_list, &list_len, fdev)) { my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; return "Failed to read Gtid_list_log_event: corrupt binlog"; @@ -1514,6 +1555,7 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, if (event_type == GTID_EVENT && using_gtid_state) { uchar flags2; + slave_connection_state::entry *gtid_entry; rpl_gtid *gtid; if (gtid_state->count() > 0 || until_gtid_state) @@ -1524,7 +1566,7 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, 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)) + &event_gtid.seq_no, &flags2, fdev)) { my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; return "Failed to read Gtid_log_event: corrupt binlog"; @@ -1551,9 +1593,28 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, if (gtid_state->count() > 0) { - gtid= gtid_state->find(event_gtid.domain_id); - if (gtid != NULL) + 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); + my_errno= 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) @@ -1563,8 +1624,7 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, event_gtid.seq_no >= gtid->seq_no) { if (slave_gtid_strict_mode && event_gtid.seq_no > gtid->seq_no && - !my_hash_search(fake_gtid_hash, - (const uchar *)&event_gtid.domain_id, 0)) + !(gtid_entry->flags & slave_connection_state::START_OWN_SLAVE_POS)) { /* In strict mode, it is an error if the slave requests to start @@ -1839,10 +1899,10 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, rpl_binlog_state until_binlog_state; bool slave_gtid_strict_mode= false; bool send_fake_gtid_list= false; - HASH fake_gtid_hash; uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; int old_max_allowed_packet= thd->variables.max_allowed_packet; + Format_description_log_event *fdev= NULL; #ifndef DBUG_OFF int left_events = max_binlog_dump_events; @@ -1853,9 +1913,6 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, bzero((char*) &log,sizeof(log)); bzero(&error_gtid, sizeof(error_gtid)); - my_hash_init(&fake_gtid_hash, &my_charset_bin, 32, - offsetof(rpl_gtid, domain_id), sizeof(uint32), NULL, my_free, - HASH_UNIQUE); /* heartbeat_period from @master_heartbeat_period user variable */ @@ -1921,6 +1978,13 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, } #endif + if (!(fdev= new Format_description_log_event(3))) + { + errmsg= "Out of memory initializing format_description event"; + my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; + goto err; + } + if (!mysql_bin_log.is_open()) { errmsg = "Binary log is not open"; @@ -1955,8 +2019,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, goto err; } if ((error= check_slave_start_position(thd, >id_state, &errmsg, - &error_gtid, until_gtid_state, - &fake_gtid_hash))) + &error_gtid, until_gtid_state))) { my_errno= error; goto err; @@ -2085,6 +2148,8 @@ impossible position"; (*packet)[EVENT_TYPE_OFFSET+ev_offset])); if ((*packet)[EVENT_TYPE_OFFSET+ev_offset] == FORMAT_DESCRIPTION_EVENT) { + Format_description_log_event *tmp; + current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset, packet->length() - ev_offset); DBUG_ASSERT(current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || @@ -2102,6 +2167,18 @@ impossible position"; "slaves that cannot process them"); goto err; } + + if (!(tmp= new Format_description_log_event(packet->ptr()+ev_offset, + packet->length()-ev_offset, + fdev))) + { + my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; + errmsg= "Corrupt Format_description event found or out-of-memory"; + goto err; + } + delete fdev; + fdev= tmp; + (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; /* mark that this event with "log_pos=0", so the slave @@ -2219,6 +2296,8 @@ impossible position"; #endif if (event_type == FORMAT_DESCRIPTION_EVENT) { + Format_description_log_event *tmp; + current_checksum_alg= get_checksum_alg(packet->ptr() + ev_offset, packet->length() - ev_offset); DBUG_ASSERT(current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || @@ -2237,6 +2316,17 @@ impossible position"; goto err; } + if (!(tmp= new Format_description_log_event(packet->ptr()+ev_offset, + packet->length()-ev_offset, + fdev))) + { + my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG; + errmsg= "Corrupt Format_description event found or out-of-memory"; + goto err; + } + delete fdev; + fdev= tmp; + (*packet)[FLAGS_OFFSET+ev_offset] &= ~LOG_EVENT_BINLOG_IN_USE_F; } @@ -2261,7 +2351,7 @@ impossible position"; until_gtid_state, >id_until_group, &until_binlog_state, slave_gtid_strict_mode, &error_gtid, - &send_fake_gtid_list, &fake_gtid_hash))) + &send_fake_gtid_list, fdev))) { errmsg= tmp_msg; goto err; @@ -2467,8 +2557,7 @@ impossible position"; >id_skip_group, until_gtid_state, >id_until_group, &until_binlog_state, slave_gtid_strict_mode, &error_gtid, - &send_fake_gtid_list, - &fake_gtid_hash))) + &send_fake_gtid_list, fdev))) { errmsg= tmp_msg; goto err; @@ -2558,7 +2647,6 @@ impossible position"; end: end_io_cache(&log); mysql_file_close(file, MYF(MY_WME)); - my_hash_free(&fake_gtid_hash); RUN_HOOK(binlog_transmit, transmit_stop, (thd, flags)); my_eof(thd); @@ -2567,6 +2655,7 @@ end: thd->current_linfo = 0; mysql_mutex_unlock(&LOCK_thread_count); thd->variables.max_allowed_packet= old_max_allowed_packet; + delete fdev; DBUG_VOID_RETURN; err: @@ -2595,6 +2684,18 @@ err: /* Use this error code so slave will know not to try reconnect. */ my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; } + else if (my_errno == ER_GTID_POSITION_NOT_FOUND_IN_BINLOG2) + { + my_snprintf(error_text, sizeof(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 errorneous transactions", + error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no); + /* Use this error code so slave will know not to try reconnect. */ + my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; + } else if (my_errno == ER_GTID_START_FROM_BINLOG_HOLE) { my_snprintf(error_text, sizeof(error_text), @@ -2629,8 +2730,8 @@ err: mysql_mutex_unlock(&LOCK_thread_count); if (file >= 0) mysql_file_close(file, MYF(MY_WME)); - my_hash_free(&fake_gtid_hash); thd->variables.max_allowed_packet= old_max_allowed_packet; + delete fdev; my_message(my_errno, error_text, MYF(0)); DBUG_VOID_RETURN; @@ -3443,7 +3544,7 @@ 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) { if (!mysql_bin_log.is_open()) { @@ -3452,7 +3553,7 @@ int reset_master(THD* thd) return 1; } - if (mysql_bin_log.reset_logs(thd, 1)) + if (mysql_bin_log.reset_logs(thd, 1, init_state, init_state_len)) return 1; RUN_HOOK(binlog_transmit, after_reset_master, (thd, 0 /* flags */)); return 0; @@ -3902,7 +4003,7 @@ rpl_append_gtid_state(String *dest, bool use_binlog) /* - Load the current GITD position into a slave_connection_state, for use when + 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 diff --git a/sql/sql_repl.h b/sql/sql_repl.h index 917da9b598e..da55e3e863f 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -46,7 +46,7 @@ int stop_slave(THD* thd, Master_info* mi, bool net_report); 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); 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); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 93f47633a95..f5cfbf8ffd1 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -106,7 +106,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, @@ -664,8 +664,8 @@ int JOIN::prepare(Item ***rref_pointer_array, TABLE_LIST *tables_init, uint wild_num, COND *conds_init, uint og_num, - ORDER *order_init, ORDER *group_init, - Item *having_init, + ORDER *order_init, bool skip_order_by, + ORDER *group_init, Item *having_init, ORDER *proc_param_init, SELECT_LEX *select_lex_arg, SELECT_LEX_UNIT *unit_arg) { @@ -779,7 +779,16 @@ JOIN::prepare(Item ***rref_pointer_array, 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 (setup_order(thd, (*rref_pointer_array), tables_list, fields_list, + all_fields, select_lex->order_list.first)) + DBUG_RETURN(-1); + select_lex->order_list.empty(); + } + if (having) { nesting_map save_allow_sum_func= thd->lex->allow_sum_func; @@ -997,20 +1006,35 @@ err: DBUG_RETURN(res); /* purecov: inspected */ } - int JOIN::optimize() { + bool was_optimized= optimized; int res= optimize_inner(); /* If we're inside a non-correlated subquery, this function may be called for the second time after the subquery has been executed and deleted. The second call will not produce a valid query plan, it will short-circuit because optimized==TRUE. + + "was_optimized != optimized" is here to handle this case: + - first optimization starts, gets an error (from a const. cheap + subquery), returns 1 + - another JOIN::optimize() call made, and now join->optimize() will + return 0, even though we never had a query plan. */ - if (!res && have_query_plan != QEP_DELETED) + if (was_optimized != optimized && !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); + } return res; } + + /** global select optimisation. @@ -1354,6 +1378,15 @@ TODO: make view to decide if it is possible to write to WHERE directly or make S /* Handle the case where we have an OUTER JOIN without a WHERE */ conds=new Item_int((longlong) 1,1); // Always true } + + if (impossible_where) + { + zero_result_cause= + "Impossible WHERE noticed after reading const tables"; + select_lex->mark_const_derived(zero_result_cause); + goto setup_subq_exit; + } + select= make_select(*table, const_table_map, const_table_map, conds, 1, &error); if (error) @@ -1458,6 +1491,12 @@ TODO: make view to decide if it is possible to write to WHERE directly or make S new store_key_const_item(*tab->ref.key_copy[key_copy_index], item); } + else if (item->const_item()) + { + tab->ref.key_copy[key_copy_index]= + new store_key_item(*tab->ref.key_copy[key_copy_index], + item, TRUE); + } else { store_key_field *field_copy= ((store_key_field *)key_copy); @@ -1598,8 +1637,10 @@ TODO: make view to decide if it is possible to write to WHERE directly or make S JOIN_TAB *tab= &join_tab[const_tables]; bool all_order_fields_used; 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))) @@ -1990,9 +2031,10 @@ int JOIN::init_execution() JOIN_TAB *last_join_tab= join_tab + top_join_tab_count - 1; do { - if (used_tables & last_join_tab->table->map) + if (used_tables & last_join_tab->table->map || + last_join_tab->use_join_cache) break; - last_join_tab->not_used_in_distinct=1; + last_join_tab->shortcut_for_distinct= true; } while (last_join_tab-- != join_tab); /* Optimize "select distinct b from t1 order by key_part_1 limit #" */ if (order && skip_sort_order) @@ -2276,29 +2318,56 @@ 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 (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); + } +} + + void JOIN::exec() { - /* - Enable SHOW EXPLAIN only if we're in the top-level query. - */ - thd->apc_target.enable(); 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); ); - exec_inner(); + if (!exec_saved_explain) + { + save_explain_data(thd->lex->explain, true /* can overwrite */, + need_tmp, + order != 0 && !skip_sort_order, + select_distinct); + exec_saved_explain= true; + } + 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); ); - - thd->apc_target.disable(); } @@ -2840,6 +2909,7 @@ void JOIN::exec_inner() 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, @@ -3140,7 +3210,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"); @@ -3174,7 +3244,7 @@ mysql_select(THD *thd, Item ***rref_pointer_array, else { if ((err= join->prepare(rref_pointer_array, tables, wild_num, - conds, og_num, order, group, having, + conds, og_num, order, false, group, having, proc_param, select_lex, unit))) { goto err; @@ -3198,7 +3268,7 @@ mysql_select(THD *thd, Item ***rref_pointer_array, 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, group, having, proc_param, + conds, og_num, order, false, group, having, proc_param, select_lex, unit))) { goto err; @@ -3234,7 +3304,7 @@ err: err|= select_lex->cleanup(); DBUG_RETURN(err || thd->is_error()); } - DBUG_RETURN(join->error); + DBUG_RETURN(join->error ? join->error: err); } @@ -3748,6 +3818,18 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, } } + join->impossible_where= false; + if (conds && const_count) + { + conds= remove_eq_conds(join->thd, conds, &join->cond_value); + if (join->cond_value == Item::COND_FALSE) + { + join->impossible_where= true; + conds=new Item_int((longlong) 0,1); + } + join->conds= conds; + } + /* Calc how many (possible) matched records in each table */ for (s=stat ; s < stat_end ; s++) @@ -3852,7 +3934,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 @@ -4949,6 +5031,33 @@ static void add_key_fields_for_nj(JOIN *join, TABLE_LIST *nested_join_table, } +void count_cond_for_nj(SELECT_LEX *sel, TABLE_LIST *nested_join_table) +{ + List_iterator<TABLE_LIST> li(nested_join_table->nested_join->join_list); + List_iterator<TABLE_LIST> li2(nested_join_table->nested_join->join_list); + bool have_another = FALSE; + TABLE_LIST *table; + + while ((table= li++) || (have_another && (li=li2, have_another=FALSE, + (table= li++)))) + if (table->nested_join) + { + if (!table->on_expr) + { + /* It's a semi-join nest. Walk into it as if it wasn't a nest */ + have_another= TRUE; + li2= li; + li= List_iterator<TABLE_LIST>(table->nested_join->join_list); + } + else + count_cond_for_nj(sel, table); + } + if (nested_join_table->on_expr) + nested_join_table->on_expr->walk(&Item::count_sargable_conds, + 0, (uchar*) sel); + +} + /** Update keyuse array with all possible keys we can use to fetch rows. @@ -4979,6 +5088,27 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, KEY_FIELD *key_fields, *end, *field; uint sz; uint m= MY_MAX(select_lex->max_equal_elems,1); + + SELECT_LEX *sel=thd->lex->current_select; + sel->cond_count= 0; + sel->between_count= 0; + if (cond) + cond->walk(&Item::count_sargable_conds, 0, (uchar*) sel); + for (i=0 ; i < tables ; i++) + { + if (*join_tab[i].on_expr_ref) + (*join_tab[i].on_expr_ref)->walk(&Item::count_sargable_conds, + 0, (uchar*) sel); + } + { + List_iterator<TABLE_LIST> li(*join_tab->join->join_list); + TABLE_LIST *table; + while ((table= li++)) + { + if (table->nested_join) + count_cond_for_nj(sel, table); + } + } /* We use the same piece of memory to store both KEY_FIELD @@ -5002,8 +5132,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, substitutions. */ sz= MY_MAX(sizeof(KEY_FIELD),sizeof(SARGABLE_PARAM))* - (((thd->lex->current_select->cond_count+1)*2 + - thd->lex->current_select->between_count)*m+1); + ((sel->cond_count*2 + sel->between_count)*m+1); if (!(key_fields=(KEY_FIELD*) thd->alloc(sz))) return TRUE; /* purecov: inspected */ and_level= 0; @@ -5209,8 +5338,23 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array) Optionally (if out_args is supplied) will push the arguments of AGGFN(DISTINCT) to the list + Check for every COUNT(DISTINCT), AVG(DISTINCT) or + SUM(DISTINCT). These can be resolved by Loose Index Scan as long + as all the aggregate distinct functions refer to the same + fields. Thus: + + SELECT AGGFN(DISTINCT a, b), AGGFN(DISTINCT b, a)... => can use LIS + SELECT AGGFN(DISTINCT a), AGGFN(DISTINCT a) ... => can use LIS + SELECT AGGFN(DISTINCT a, b), AGGFN(DISTINCT a) ... => cannot use LIS + SELECT AGGFN(DISTINCT a), AGGFN(DISTINCT b) ... => cannot use LIS + etc. + @param join the join to check - @param[out] out_args list of aggregate function arguments + @param[out] out_args Collect the arguments of the aggregate functions + to a list. We don't worry about duplicates as + these will be sorted out later in + get_best_group_min_max. + @return does the query qualify for indexed AGGFN(DISTINCT) @retval true it does @retval false AGGFN(DISTINCT) must apply distinct in it. @@ -5221,6 +5365,7 @@ is_indexed_agg_distinct(JOIN *join, List<Item_field> *out_args) { Item_sum **sum_item_ptr; bool result= false; + Field_map first_aggdistinct_fields; if (join->table_count != 1 || /* reference more than 1 table */ join->select_distinct || /* or a DISTINCT */ @@ -5233,6 +5378,7 @@ is_indexed_agg_distinct(JOIN *join, List<Item_field> *out_args) for (sum_item_ptr= join->sum_funcs; *sum_item_ptr; sum_item_ptr++) { Item_sum *sum_item= *sum_item_ptr; + Field_map cur_aggdistinct_fields; Item *expr; /* aggregate is not AGGFN(DISTINCT) or more than 1 argument to it */ switch (sum_item->sum_func()) @@ -5262,15 +5408,23 @@ is_indexed_agg_distinct(JOIN *join, List<Item_field> *out_args) if (expr->real_item()->type() != Item::FIELD_ITEM) return false; - /* - If we came to this point the AGGFN(DISTINCT) loose index scan - optimization is applicable - */ + Item_field* item= static_cast<Item_field*>(expr->real_item()); if (out_args) - out_args->push_back((Item_field *) expr->real_item()); + out_args->push_back(item); + + cur_aggdistinct_fields.set_bit(item->field->field_index); result= true; } + /* + If there are multiple aggregate functions, make sure that they all + refer to exactly the same set of columns. + */ + if (first_aggdistinct_fields.is_clear_all()) + first_aggdistinct_fields.merge(cur_aggdistinct_fields); + else if (first_aggdistinct_fields != cur_aggdistinct_fields) + return false; } + return result; } @@ -6229,8 +6383,11 @@ 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 { @@ -7122,7 +7279,8 @@ double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s, { if (!(next_field->table->map & rem_tables) && next_field->table != table) { - sel/= field->cond_selectivity; + if (field->cond_selectivity > 0) + sel/= field->cond_selectivity; break; } } @@ -7267,6 +7425,14 @@ best_extension_by_limited_search(JOIN *join, DBUG_ENTER("best_extension_by_limited_search"); THD *thd= join->thd; + + 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); @@ -7429,7 +7595,7 @@ 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; @@ -7480,20 +7646,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); } @@ -8605,6 +8781,7 @@ JOIN::make_simple_join(JOIN *parent, TABLE *temp_table) join_tab->keys.init(); join_tab->keys.set_all(); /* test everything in quick */ join_tab->ref.key = -1; + join_tab->shortcut_for_distinct= false; join_tab->read_first_record= join_init_read_record; join_tab->join= this; join_tab->ref.key_parts= 0; @@ -8703,14 +8880,13 @@ static void add_not_null_conds(JOIN *join) Item *item= tab->ref.items[keypart]; Item *notnull; Item *real= item->real_item(); - if (real->basic_const_item()) + if (real->const_item() && real->type() != Item::FIELD_ITEM && + !real->is_expensive()) { /* It could be constant instead of field after constant propagation. */ - DBUG_ASSERT(real->is_expensive() || // prevent early expensive eval - !real->is_null()); // NULLs are not propagated continue; } DBUG_ASSERT(real->type() == Item::FIELD_ITEM); @@ -8880,7 +9056,7 @@ make_outerjoin_info(JOIN *join) TABLE_LIST *tbl= table->pos_in_table_list; TABLE_LIST *embedding= tbl->embedding; - if (tbl->outer_join) + if (tbl->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT)) { /* Table tab is the only one inner table for outer join. @@ -8954,10 +9130,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 @@ -9301,19 +9473,18 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) else { sel->needed_reg=tab->needed_reg; - sel->quick_keys.clear_all(); } + sel->quick_keys= tab->table->quick_keys; if (!sel->quick_keys.is_subset(tab->checked_keys) || !sel->needed_reg.is_subset(tab->checked_keys)) { - tab->keys=sel->quick_keys; - tab->keys.merge(sel->needed_reg); tab->use_quick= (!sel->needed_reg.is_clear_all() && - (select->quick_keys.is_clear_all() || - (select->quick && - (select->quick->records >= 100L)))) ? + (sel->quick_keys.is_clear_all() || + (sel->quick && + (sel->quick->records >= 100L)))) ? 2 : 1; sel->read_tables= used_tables & ~current_map; + sel->quick_keys.clear_all(); } if (i != join->const_tables && tab->use_quick != 2 && !tab->first_inner) @@ -10380,6 +10551,86 @@ 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 Item_cond_and; + + 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= false; + scan_cond_li.rewind(); + while ((scan_item= scan_cond_li++)) + { + if (pushed_item->eq(scan_item, 0)) + { + found= true; + break; + } + } + if (!found) + reduced_select_cond->add(pushed_item); + } + } + else + { + while ((pushed_item= pushed_cond_li++)) + { + if (!pushed_item->eq(cache_select->cond, 0)) + reduced_select_cond->add(pushed_item); + } + } + + /* + 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 @@ -10629,6 +10880,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; @@ -10736,6 +10996,11 @@ void JOIN_TAB::cleanup() { if (table->pos_in_table_list->jtbm_subselect->is_jtbm_const_tab) { + /* + Set this to NULL so that cleanup_empty_jtbm_semi_joins() doesn't + attempt to make another free_tmp_table call. + */ + table->pos_in_table_list->table= NULL; free_tmp_table(join->thd, table); table= NULL; } @@ -11087,7 +11352,8 @@ void JOIN::cleanup(bool full) DBUG_ENTER("JOIN::cleanup"); DBUG_PRINT("enter", ("full %u", (uint) full)); - have_query_plan= QEP_DELETED; + if (full) + have_query_plan= QEP_DELETED; if (table) { @@ -11159,6 +11425,7 @@ void JOIN::cleanup(bool full) } if (full) { + cleanup_empty_jtbm_semi_joins(this); /* Ensure that the following delete_elements() would not be called twice for the same list. @@ -11958,13 +12225,10 @@ static bool check_row_equality(THD *thd, Item *left_row, Item_row *right_row, (Item_row *) left_item, (Item_row *) right_item, cond_equal, eq_list); - if (!is_converted) - thd->lex->current_select->cond_count++; } else { is_converted= check_simple_equality(left_item, right_item, 0, cond_equal); - thd->lex->current_select->cond_count++; } if (!is_converted) @@ -12023,7 +12287,6 @@ static bool check_equality(THD *thd, Item *item, COND_EQUAL *cond_equal, if (left_item->type() == Item::ROW_ITEM && right_item->type() == Item::ROW_ITEM) { - thd->lex->current_select->cond_count--; return check_row_equality(thd, (Item_row *) left_item, (Item_row *) right_item, @@ -12665,12 +12928,14 @@ Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, /* If we're inside an SJM-nest (current_sjm!=NULL), and the multi-equality doesn't include a constant, we should produce equality with the first - of the equals in this SJM. + of the equal items in this SJM (except for the first element inside the + SJM. For that, we produce the equality with the "head" item). In other cases, get the "head" item, which is either first of the equals on top level, or the constant. */ - Item *head_item= (!item_const && current_sjm)? current_sjm_head: head; + Item *head_item= (!item_const && current_sjm && + current_sjm_head != field_item) ? current_sjm_head: head; Item *head_real_item= head_item->real_item(); if (head_real_item->type() == Item::FIELD_ITEM) head_item= head_real_item; @@ -13328,7 +13593,8 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top, table->embedding->nested_join->not_null_tables|= not_null_tables; } - if (!table->outer_join || (used_tables & not_null_tables)) + if (!(table->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT)) || + (used_tables & not_null_tables)) { /* For some of the inner tables there are conjunctive predicates @@ -13904,22 +14170,230 @@ optimize_cond(JOIN *join, COND *conds, /** - Handles the recursive job remove_eq_conds() + @brief + Propagate multiple equalities to the sub-expressions of a condition - 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 ) + @param thd thread handle + @param cond the condition where equalities are to be propagated + @param *new_equalities the multiple equalities to be propagated + @param inherited path to all inherited multiple equality items + @param[out] is_simplifiable_cond 'cond' may be simplified after the + propagation of the equalities + + @details + The function recursively traverses the tree of the condition 'cond' and + for each its AND sub-level of any depth the function merges the multiple + equalities from the list 'new_equalities' into the multiple equalities + attached to the AND item created for this sub-level. + The function also [re]sets references to the equalities formed by the + merges of multiple equalities in all field items occurred in 'cond' + that are encountered in the equalities. + If the result of any merge of multiple equalities is an impossible + condition the function returns TRUE in the parameter is_simplifiable_cond. +*/ - SYNOPSIS - internal_remove_eq_conds() - thd THD environment - cond the condition to handle - cond_value the resulting value of the condition +void propagate_new_equalities(THD *thd, Item *cond, + List<Item_equal> *new_equalities, + COND_EQUAL *inherited, + bool *is_simplifiable_cond) +{ + if (cond->type() == Item::COND_ITEM) + { + bool and_level= ((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC; + 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; + 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); + } + List_iterator<Item_equal> ei(*cond_equalities); + while ((equal_item= ei++)) + { + if (equal_item->const_item() && !equal_item->val_int()) + { + *is_simplifiable_cond= true; + return; + } + } + } + } - RETURN - *COND with the simplified condition + Item *item; + List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); + while ((item= li++)) + { + COND_EQUAL *new_inherited= and_level && item->type() == Item::COND_ITEM ? + &((Item_cond_and *) cond)->cond_equal : + inherited; + propagate_new_equalities(thd, item, new_equalities, new_inherited, + is_simplifiable_cond); + } + } + else if (cond->type() == Item::FUNC_ITEM && + ((Item_cond*) cond)->functype() == Item_func::MULT_EQUAL_FUNC) + { + Item_equal *equal_item; + List_iterator<Item_equal> it(*new_equalities); + Item_equal *equality= (Item_equal *) cond; + equality->upper_levels= inherited; + while ((equal_item= it++)) + { + equality->merge_with_check(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->update_used_tables(); + } +} + +/* + Check if cond_is_datetime_is_null() is true for the condition cond, or + for any of its AND/OR-children +*/ +bool cond_has_datetime_is_null(Item *cond) +{ + if (cond_is_datetime_is_null(cond)) + return true; + + if (cond->type() == Item::COND_ITEM) + { + List<Item> *cond_arg_list= ((Item_cond*) cond)->argument_list(); + List_iterator<Item> li(*cond_arg_list); + Item *item; + while ((item= li++)) + { + if (cond_has_datetime_is_null(item)) + return true; + } + } + return false; +} + +/* + Check if passed condtition has for of + + not_null_date_col IS NULL + + where not_null_date_col has a datte or datetime type +*/ + +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]->type() == Item::FIELD_ITEM) + { + Field *field=((Item_field*) args[0])->field; + + if (((field->type() == MYSQL_TYPE_DATE) || + (field->type() == MYSQL_TYPE_DATETIME)) && + (field->flags & NOT_NULL_FLAG)) + { + return TRUE; + } + } + } + return FALSE; +} + + +/** + @brief + Evaluate all constant boolean sub-expressions in a condition + + @param thd thread handle + @param cond condition where where to evaluate constant sub-expressions + @param[out] cond_value : the returned value of the condition + (TRUE/FALSE/UNKNOWN: + Item::COND_TRUE/Item::COND_FALSE/Item::COND_OK) + @return + the item that is the result of the substitution of all inexpensive constant + boolean sub-expressions into cond, or, + NULL if the condition is constant and is evaluated to FALSE. + + @details + This function looks for all inexpensive constant boolean sub-expressions in + the given condition 'cond' and substitutes them for their values. + For example, the condition 2 > (5 + 1) or a < (10 / 2) + will be transformed to the condition a < (10 / 2). + Note that a constant sub-expression is evaluated only if it is constant and + inexpensive. A sub-expression with an uncorrelated subquery may be evaluated + only if the subquery is considered as inexpensive. + The function does not evaluate a constant sub-expression if it is not on one + of AND/OR levels of the condition 'cond'. For example, the subquery in the + condition a > (select max(b) from t1 where b > 5) will never be evaluated + by this function. + If a constant boolean sub-expression is evaluated to TRUE then: + - when the sub-expression is a conjunct of an AND formula it is simply + removed from this formula + - when the sub-expression is a disjunct of an OR formula the whole OR + formula is converted to TRUE + If a constant boolean sub-expression is evaluated to FALSE then: + - when the sub-expression is a disjunct of an OR formula it is simply + removed from this formula + - when the sub-expression is a conjuct of an AND formula the whole AND + formula is converted to FALSE + When a disjunct/conjunct is removed from an OR/AND formula it might happen + that there is only one conjunct/disjunct remaining. In this case this + remaining disjunct/conjunct must be merged into underlying AND/OR formula, + because AND/OR levels must alternate in the same way as they alternate + after fix_fields() is called for the original condition. + The specifics of merging a formula f into an AND formula A appears + when A contains multiple equalities and f contains multiple equalities. + In this case the multiple equalities from f and A have to be merged. + After this the resulting multiple equalities have to be propagated into + the all AND/OR levels of the formula A (see propagate_new_equalities()). + The propagation of multiple equalities might result in forming multiple + equalities that are always FALSE. This, in its turn, might trigger further + simplification of the condition. + + @note + EXAMPLE 1: + SELECT * FROM t1 WHERE (b = 1 OR a = 1) AND (b = 5 AND a = 5 OR 1 != 1); + First 1 != 1 will be removed from the second conjunct: + => SELECT * FROM t1 WHERE (b = 1 OR a = 1) AND (b = 5 AND a = 5); + Then (b = 5 AND a = 5) will be merged into the top level condition: + => SELECT * FROM t1 WHERE (b = 1 OR a = 1) AND (b = 5) AND (a = 5); + Then (b = 5), (a = 5) will be propagated into the disjuncs of + (b = 1 OR a = 1): + => SELECT * FROM t1 WHERE ((b = 1) AND (b = 5) AND (a = 5) OR + (a = 1) AND (b = 5) AND (a = 5)) AND + (b = 5) AND (a = 5) + => SELECT * FROM t1 WHERE ((FALSE AND (a = 5)) OR + (FALSE AND (b = 5))) AND + (b = 5) AND (a = 5) + After this an additional call of remove_eq_conds() converts it + to FALSE + + EXAMPLE 2: + SELECT * FROM t1 WHERE (b = 1 OR a = 5) AND (b = 5 AND a = 5 OR 1 != 1); + => SELECT * FROM t1 WHERE (b = 1 OR a = 5) AND (b = 5 AND a = 5); + => SELECT * FROM t1 WHERE (b = 1 OR a = 5) AND (b = 5) AND (a = 5); + => SELECT * FROM t1 WHERE ((b = 1) AND (b = 5) AND (a = 5) OR + (a = 5) AND (b = 5) AND (a = 5)) AND + (b = 5) AND (a = 5) + => SELECT * FROM t1 WHERE ((FALSE AND (a = 5)) OR + ((b = 5) AND (a = 5))) AND + (b = 5) AND (a = 5) + After this an additional call of remove_eq_conds() converts it to + => SELECT * FROM t1 WHERE (b = 5) AND (a = 5) */ static COND * @@ -13927,9 +14401,11 @@ internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) { if (cond->type() == Item::COND_ITEM) { + List<Item_equal> new_equalities; bool and_level= ((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC; - List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); + List<Item> *cond_arg_list= ((Item_cond*) cond)->argument_list(); + List_iterator<Item> li(*cond_arg_list); Item::cond_result tmp_cond_value; bool should_fix_fields=0; @@ -13939,92 +14415,86 @@ internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) { 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) { - if (and_level) - { - /* - Take a special care of multiple equality predicates - that may be part of 'cond' and 'new_item'. - Those multiple equalities that have common members - must be merged. - */ - Item_cond_and *cond_and= (Item_cond_and *) cond; - List<Item_equal> *cond_equal_items= - &cond_and->cond_equal.current_level; - List<Item> *cond_and_list= cond_and->argument_list(); - - if (new_item->type() == Item::COND_ITEM && - ((Item_cond*) new_item)->functype() == Item_func::COND_AND_FUNC) - { - Item_cond_and *new_item_and= (Item_cond_and *) new_item; - List<Item_equal> *new_item_equal_items= - &new_item_and->cond_equal.current_level; - List<Item> *new_item_and_list= new_item_and->argument_list(); - cond_and_list->disjoin((List<Item>*) cond_equal_items); - new_item_and_list->disjoin((List<Item>*) new_item_equal_items); - Item_equal *equal_item; - List_iterator<Item_equal> it(*new_item_equal_items); - while ((equal_item= it++)) - { - equal_item->merge_into_list(cond_equal_items); - } - if (new_item_and_list->is_empty()) - li.remove(); - else + /* + 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(((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)->cond_equal.current_level; + if (!new_item_equalities->is_empty()) { - Item *list_item; - Item *new_list_item; - uint cnt= new_item_and_list->elements; - List_iterator<Item> it(*new_item_and_list); - while ((list_item= it++)) - { - uchar* is_subst_valid= (uchar *) Item::ANY_SUBST; - new_list_item= - list_item->compile(&Item::subst_argument_checker, - &is_subst_valid, - &Item::equal_fields_propagator, - (uchar *) &cond_and->cond_equal); - if (new_list_item != list_item) - it.replace(new_list_item); - new_list_item->update_used_tables(); - } - li.replace(*new_item_and_list); - for (cnt--; cnt; cnt--) - item= li++; + /* + 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); } - cond_and_list->concat((List<Item>*) cond_equal_items); } - else if (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(); + else { - cond_and_list->disjoin((List<Item>*) cond_equal_items); - ((Item_equal *) new_item)->merge_into_list(cond_equal_items); - li.remove(); - cond_and_list->concat((List<Item>*) cond_equal_items); + 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; } - else - li.replace(new_item); + } + else if (and_level && + new_item->type() == Item::FUNC_ITEM && + ((Item_cond*) new_item)->functype() == + Item_func::MULT_EQUAL_FUNC) + { + 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> *arg_list= ((Item_cond*) new_item)->argument_list(); - uint cnt= arg_list->elements; - li.replace(*arg_list); - for ( cnt--; cnt; cnt--) - item= li++; + 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 + else li.replace(new_item); + should_fix_fields= 1; } - should_fix_fields=1; - } + } if (*cond_value == Item::COND_UNDEF) *cond_value=tmp_cond_value; switch (tmp_cond_value) { @@ -14050,6 +14520,55 @@ internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) 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) + { + if (!(cond= internal_remove_eq_conds(thd, cond, cond_value))) + return cond; + } + should_fix_fields= 1; + } if (should_fix_fields) cond->update_used_tables(); @@ -14063,53 +14582,45 @@ internal_remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) return item; } } - else if (cond->type() == Item::FUNC_ITEM && - ((Item_func*) cond)->functype() == Item_func::ISNULL_FUNC) + else if (cond_is_datetime_is_null(cond)) { - Item_func_isnull *func=(Item_func_isnull*) cond; - Item **args= func->arguments(); - if (args[0]->type() == Item::FIELD_ITEM) - { - Field *field=((Item_field*) args[0])->field; - /* 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' + /* 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) + 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) - */ - if (((field->type() == MYSQL_TYPE_DATE) || - (field->type() == MYSQL_TYPE_DATETIME)) && - (field->flags & NOT_NULL_FLAG)) - { - 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; + */ + Item **args= ((Item_func_isnull*) cond)->arguments(); + Field *field=((Item_field*) args[0])->field; - if (field->table->pos_in_table_list->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; - } - else - { - // not outer join: transform "col IS NULL" to "col=0" - cond= eq_cond; - } + 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; - cond->fix_fields(thd, &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; } + else + { + // not outer join: transform "col IS NULL" to "col=0" + cond= eq_cond; + } + + cond->fix_fields(thd, &cond); + if (cond->const_item() && !cond->is_expensive()) { *cond_value= eval_const_cond(cond) ? Item::COND_TRUE : Item::COND_FALSE; @@ -14214,7 +14725,7 @@ remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value) } -/* +/** Check if equality can be used in removing components of GROUP BY/DISTINCT @param l the left comparison argument (a field if any) @@ -14796,7 +15307,8 @@ TABLE * create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, ORDER *group, bool distinct, bool save_sum_fields, ulonglong select_options, ha_rows rows_limit, - const char *table_alias, bool do_not_open) + const char *table_alias, bool do_not_open, + bool keep_row_order) { MEM_ROOT *mem_root_save, own_root; TABLE *table; @@ -15611,6 +16123,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, share->db_record_offset= 1; table->used_for_duplicate_elimination= (param->sum_func_count == 0 && (table->group || table->distinct)); + table->keep_row_order= keep_row_order; if (!do_not_open) { @@ -15947,7 +16460,8 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo, table->no_rows ? NO_RECORD : (share->reclength < 64 && !share->blob_fields ? STATIC_RECORD : - table->used_for_duplicate_elimination ? + table->used_for_duplicate_elimination || + table->keep_row_order ? DYNAMIC_RECORD : BLOCK_RECORD), share->keys, &keydef, (uint) (*recinfo-start_recinfo), @@ -16758,7 +17272,15 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) { DBUG_ENTER("sub_select"); - join_tab->table->null_row=0; + if (join_tab->last_inner) + { + JOIN_TAB *last_inner_tab= join_tab->last_inner; + for (JOIN_TAB *jt= join_tab; jt <= last_inner_tab; jt++) + jt->table->null_row= 0; + } + else + join_tab->table->null_row=0; + if (end_of_records) { enum_nested_loop_state nls= @@ -16879,7 +17401,7 @@ static enum_nested_loop_state evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, int error) { - bool not_used_in_distinct=join_tab->not_used_in_distinct; + bool shortcut_for_distinct= join_tab->shortcut_for_distinct; ha_rows found_records=join->found_records; COND *select_cond= join_tab->select_cond; bool select_cond_result= TRUE; @@ -17044,7 +17566,7 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, was not in the field list; In this case we can abort if we found a row, as no new rows can be added to the result. */ - if (not_used_in_distinct && found_records != join->found_records) + if (shortcut_for_distinct && found_records != join->found_records) DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS); } else @@ -17171,7 +17693,7 @@ int report_error(TABLE *table, int error) print them to the .err log */ if (error != HA_ERR_LOCK_DEADLOCK && error != HA_ERR_LOCK_WAIT_TIMEOUT - && !table->in_use->killed) + && error != HA_ERR_TABLE_DEF_CHANGED && !table->in_use->killed) sql_print_error("Got error %d when reading table '%s'", error, table->s->path.str); table->file->print_error(error,MYF(0)); @@ -17236,7 +17758,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; @@ -17262,7 +17784,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; @@ -17545,6 +18067,11 @@ join_read_always_key(JOIN_TAB *tab) if (cp_buffer_from_ref(tab->join->thd, table, &tab->ref)) return -1; + if ((error= table->file->prepare_index_key_scan_map(tab->ref.key_buff, make_prev_keypart_map(tab->ref.key_parts)))) + { + report_error(table,error); + return -1; + } if ((error= table->file->ha_index_read_map(table->record[0], tab->ref.key_buff, make_prev_keypart_map(tab->ref.key_parts), @@ -17578,6 +18105,11 @@ join_read_last_key(JOIN_TAB *tab) if (cp_buffer_from_ref(tab->join->thd, table, &tab->ref)) return -1; + if ((error= table->file->prepare_index_key_scan_map(tab->ref.key_buff, make_prev_keypart_map(tab->ref.key_parts)))) + { + report_error(table,error); + return -1; + } if ((error= table->file->ha_index_read_map(table->record[0], tab->ref.key_buff, make_prev_keypart_map(tab->ref.key_parts), @@ -21314,22 +21846,20 @@ 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((Item_func_set_user_var*) item); + new Item_func_set_user_var(thd, (Item_func_set_user_var*) item); Item_field *new_field= new Item_field(field); - if (!suv || !new_field || suv->fix_fields(thd, (Item**)&suv)) + if (!suv || !new_field) DBUG_RETURN(true); // Fatal error - ((Item *)suv)->name= item->name; /* - 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; @@ -21360,15 +21890,15 @@ change_to_use_tmp_fields(THD *thd, Item **ref_pointer_array, ifield->db_name= iref->db_name; } #ifndef DBUG_OFF - if (!item_field->name) - { - char buff[256]; - String str(buff,sizeof(buff),&my_charset_bin); - str.length(0); - str.extra_allocation(1024); - item->print(&str, QT_ORDINARY); - item_field->name= sql_strmake(str.ptr(),str.length()); - } + if (!item_field->name) + { + char buff[256]; + String str(buff,sizeof(buff),&my_charset_bin); + str.length(0); + str.extra_allocation(1024); + item->print(&str, QT_ORDINARY); + item_field->name= sql_strmake(str.ptr(),str.length()); + } #endif } else @@ -22121,32 +22651,167 @@ void JOIN::clear() /* Print an EXPLAIN line with all NULLs and given message in the 'Extra' column */ + int print_explain_message_line(select_result_sink *result, - SELECT_LEX *select_lex, - bool on_the_fly, uint8 options, + uint select_number, + const char *select_type, + ha_rows *rows, const char *message) { const CHARSET_INFO *cs= system_charset_info; Item *item_null= new Item_null(); List<Item> item_list; - if (on_the_fly) - select_lex->set_explain_type(on_the_fly); + item_list.push_back(new Item_int((int32) select_number)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); + /* `table` */ + item_list.push_back(item_null); + + /* `partitions` */ + if (options & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + + /* type, possible_keys, key, key_len, ref */ + for (uint i=0 ; i < 5; i++) + item_list.push_back(item_null); - item_list.push_back(new Item_int((int32) - select_lex->select_number)); - item_list.push_back(new Item_string(select_lex->type, - strlen(select_lex->type), cs)); - for (uint i=0 ; i < 7; i++) + /* `rows` */ + if (rows) + { + item_list.push_back(new Item_int(*rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + + /* `filtered` */ + if (options & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + /* `Extra` */ + if (message) + item_list.push_back(new Item_string(message,strlen(message),cs)); + else item_list.push_back(item_null); + + if (result->send_data(item_list)) + return 1; + return 0; +} + + +/* + Make a comma-separated list of possible_keys names and add it into the string +*/ + +void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line) +{ + if (!possible_keys.is_clear_all()) + { + uint j; + for (j=0 ; j < table->s->keys ; j++) + { + if (possible_keys.is_set(j)) + { + if (line->length()) + line->append(','); + line->append(table->key_info[j].name, + strlen(table->key_info[j].name), + system_charset_info); + } + } + } +} + +/* + 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 +*/ + +int print_explain_row(select_result_sink *result, + uint8 options, + uint select_number, + const char *select_type, + const char *table_name, + const char *partitions, + enum join_type jtype, + const char *possible_keys, + const char *index, + const char *key_len, + const char *ref, + ha_rows *rows, + const char *extra) +{ + const CHARSET_INFO *cs= system_charset_info; + Item *item_null= new Item_null(); + List<Item> item_list; + Item *item; + + item_list.push_back(new Item_int((int32) select_number)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); + item_list.push_back(new Item_string(table_name, + strlen(table_name), cs)); if (options & DESCRIBE_PARTITIONS) + { + if (partitions) + { + item_list.push_back(new Item_string(partitions, + strlen(partitions), cs)); + } + else + item_list.push_back(item_null); + } + + const char *jtype_str= join_type_str[jtype]; + item_list.push_back(new Item_string(jtype_str, + strlen(jtype_str), cs)); + + item= possible_keys? new Item_string(possible_keys, strlen(possible_keys), + cs) : item_null; + item_list.push_back(item); + + /* 'index */ + item= index ? new Item_string(index, strlen(index), cs) : item_null; + item_list.push_back(item); + + /* 'key_len */ + item= key_len ? new Item_string(key_len, strlen(key_len), cs) : item_null; + item_list.push_back(item); + + /* 'ref' */ + item= ref ? new Item_string(ref, strlen(ref), cs) : item_null; + item_list.push_back(item); + + /* 'rows' */ + if (rows) + { + item_list.push_back(new Item_int(*rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else item_list.push_back(item_null); + + /* 'filtered' */ + const double filtered=100.0; if (options & DESCRIBE_EXTENDED) + item_list.push_back(new Item_float(filtered, 2)); + + /* 'Extra' */ + if (extra) + item_list.push_back(new Item_string(extra, strlen(extra), cs)); + else item_list.push_back(item_null); - item_list.push_back(new Item_string(message,strlen(message),cs)); - if (result->send_data(item_list)) return 1; return 0; @@ -22232,102 +22897,118 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, } -/** - EXPLAIN handling. +/* + 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); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// TODO: join with make_possible_keys_line ? +void append_possible_keys(String *str, TABLE *table, key_map possible_keys) +{ + uint j; + for (j=0 ; j < table->s->keys ; j++) + { + if (possible_keys.is_set(j)) + { + if (str->length()) + str->append(','); + str->append(table->key_info[j].name, + strlen(table->key_info[j].name), + system_charset_info); + } + } +} - Produce lines explaining execution of *this* select (not including children - selects) - @param on_the_fly TRUE <=> we're being executed on-the-fly, so don't make - modifications to any select's data structures + +/* + Save Query Plan Footprint + + @note + Currently, this function may be called multiple times */ -int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, - bool on_the_fly, - bool need_tmp_table, bool need_order, - bool distinct, const char *message) +int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, + bool need_order, bool distinct, + const char *message) { - List<Item> field_list; - List<Item> item_list; + Explain_node *explain_node; JOIN *join= this; /* Legacy: this code used to be a non-member function */ THD *thd=join->thd; - Item *item_null= new Item_null(); - CHARSET_INFO *cs= system_charset_info; + const CHARSET_INFO *cs= system_charset_info; int quick_type; int error= 0; - DBUG_ENTER("JOIN::print_explain"); + 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(on_the_fly? have_query_plan == QEP_AVAILABLE: TRUE); + DBUG_ASSERT(have_query_plan == QEP_AVAILABLE); /* Don't log this into the slow query log */ - if (!on_the_fly) - { - 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() - */ if (message) { - if (print_explain_message_line(result, join->select_lex, on_the_fly, - explain_flags, message)) - error= 1; + Explain_select *xpl_sel; + explain_node= xpl_sel= new (output->mem_root) Explain_select; + join->select_lex->set_explain_type(true); + xpl_sel->select_id= join->select_lex->select_number; + xpl_sel->select_type= join->select_lex->type; + xpl_sel->message= message; + /* Setting xpl_sel->message means that all other members are invalid */ + output->add_node(xpl_sel); } else if (join->select_lex == join->unit->fake_select_lex) { - if (print_fake_select_lex_join(result, on_the_fly, - join->select_lex, - explain_flags)) - error= 1; + /* Do nothing, Explain_union will create and print fake_select_lex */ } else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) { + Explain_select *xpl_sel; + explain_node= xpl_sel= new (output->mem_root) Explain_select; table_map used_tables=0; - //if (!join->select_lex->type) - if (on_the_fly) - join->select_lex->set_explain_type(on_the_fly); - bool printing_materialize_nest= FALSE; - uint select_id= join->select_lex->select_number; + join->select_lex->set_explain_type(true); + xpl_sel->select_id= join->select_lex->select_number; + xpl_sel->select_type= join->select_lex->type; + JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) { + uint select_id; 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; } - + else + select_id= join->select_lex->select_number; + 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]; + char buff4[512]; 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; QUICK_SELECT_I *quick= NULL; @@ -22347,28 +23028,19 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, tab= pre_sort_join_tab; } - item_list.empty(); + Explain_table_access *eta= new (output->mem_root) Explain_table_access; + xpl_sel->add_table(eta); + eta->key.set(thd->mem_root, NULL, (uint)-1); + eta->quick_info= NULL; + /* id */ - item_list.push_back(new Item_uint((uint32)select_id)); + if (tab->bush_root_tab) + eta->sjm_nest_select_id= select_id; + else + eta->sjm_nest_select_id= 0; + /* select_type */ - const char* stype= printing_materialize_nest? "MATERIALIZED" : - join->select_lex->type; - item_list.push_back(new Item_string(stype, strlen(stype), cs)); - - enum join_type tab_type= tab->type; - if ((tab->type == JT_ALL || tab->type == JT_HASH) && - tab->select && tab->select->quick && tab->use_quick != 2) - { - quick= 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; - else - tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; - } + xpl_sel->select_type= join->select_lex->type; /* table */ if (table->derived_select_number) @@ -22377,7 +23049,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, 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)); + eta->table_name.copy(table_name_buffer, len, cs); } else if (tab->bush_children) { @@ -22387,59 +23059,53 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, 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)); + eta->table_name.copy(table_name_buffer, len, cs); } else { 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->table_name.copy(real_table->alias, strlen(real_table->alias), cs); } + /* "partitions" column */ - if (explain_flags & DESCRIBE_PARTITIONS) { #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); + make_used_partitions_str(part_info, &eta->used_partitions); + eta->used_partitions_set= true; } else - item_list.push_back(item_null); + eta->used_partitions_set= false; #else /* just produce empty column if partitioning is not compiled in */ - item_list.push_back(item_null); + eta->used_partitions_set= false; #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); - } - } + enum join_type tab_type= tab->type; + if ((tab->type == JT_ALL || tab->type == JT_HASH) && + tab->select && tab->select->quick && tab->use_quick != 2) + { + quick= 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; + else + tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; } - if (tmp1.length()) - item_list.push_back(new Item_string(tmp1.ptr(),tmp1.length(),cs)); - else - item_list.push_back(item_null); + eta->type= tab_type; - /* Build "key", "key_len", and "ref" values and add them to item_list */ + /* Build "possible_keys" value */ + append_possible_keys(&eta->possible_keys_str, table, tab->keys); + + /* Build "key", "key_len", and "ref" */ if (tab_type == JT_NEXT) { key_info= table->key_info+tab->index; @@ -22450,15 +23116,20 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, key_info= tab->get_keyinfo_by_key_no(tab->ref.key); key_len= tab->ref.key_length; } - if (key_info) + + /* + In STRAIGHT_JOIN queries, there can be join tabs with JT_CONST type + that still have quick selects. + */ + if (tab->select && tab->select->quick && tab_type != JT_CONST) + { + eta->quick_info= tab->select->quick->get_explain(thd->mem_root); + } + + if (key_info) /* 'index' or 'ref' access */ { - 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); + eta->key.set(thd->mem_root, key_info->name, key_len); + if (tab->ref.key_parts && tab_type != JT_FT) { store_key **ref=tab->ref.key_copy; @@ -22477,37 +23148,23 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, } } } - if (is_hj && tab_type != JT_HASH) + + if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */ { - tmp2.append(':'); - tmp3.append(':'); + eta->hash_next_key.set(thd->mem_root, + table->key_info[tab->index].name, + table->key_info[tab->index].key_length); } - if (tab_type == JT_HASH_NEXT) + + if (key_info) { - 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 && quick) - tab->select->quick->add_keys_and_lengths(&tmp2, &tmp3); - if (key_info || (tab->select && quick)) - { - 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)); + { + eta->ref.copy(tmp4); + eta->ref_set= true; + } else - item_list.push_back(item_null); + eta->ref_set= false; } else { @@ -22517,66 +23174,61 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, { const char *tmp_buff; int f_idx; + StringBuffer<64> key_name_buf; if (table_list->has_db_lookup_value) { + /* 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; - tmp2.append(tmp_buff, strlen(tmp_buff), cs); + key_name_buf.append(tmp_buff, strlen(tmp_buff), cs); } if (table_list->has_table_lookup_value) { if (table_list->has_db_lookup_value) - tmp2.append(','); + key_name_buf.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); + key_name_buf.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); + + if (key_name_buf.length()) + eta->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1); } - else - item_list.push_back(item_null); - item_list.push_back(item_null); - item_list.push_back(item_null); + eta->ref_set= false; } - /* Add "rows" field to item_list. */ + /* "rows" */ if (table_list /* SJM bushes don't have table_list */ && table_list->schema_table) { - /* in_rows */ - if (explain_flags & DESCRIBE_EXTENDED) - item_list.push_back(item_null); - /* rows */ - item_list.push_back(item_null); + /* I_S tables have rows=extra=NULL */ + eta->rows_set= false; + eta->filtered_set= false; } else { double examined_rows= tab->get_examined_rows(); - item_list.push_back(new Item_int((longlong) (ulonglong) examined_rows, - MY_INT64_NUM_DECIMAL_DIGITS)); + eta->rows_set= true; + eta->rows= examined_rows; - /* Add "filtered" field to item_list. */ - if (explain_flags & DESCRIBE_EXTENDED) + /* "filtered" */ + float f= 0.0; + if (examined_rows) { - float f= 0.0; - if (examined_rows) - { - double pushdown_cond_selectivity= tab->cond_selectivity; - if (pushdown_cond_selectivity == 1.0) - f= (float) (100.0 * tab->records_read / examined_rows); - else - f= (float) (100.0 * pushdown_cond_selectivity); - } - set_if_smaller(f, 100.0); - item_list.push_back(new Item_float(f, 2)); + double pushdown_cond_selectivity= tab->cond_selectivity; + if (pushdown_cond_selectivity == 1.0) + f= (float) (100.0 * tab->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 add it to item_list. */ + /* 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(tab->index)) @@ -22586,24 +23238,17 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, key_read=1; if (tab->info) - item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs)); + { + eta->push_extra(tab->info); + } else if (tab->packed_info & TAB_INFO_HAVE_VALUE) { if (tab->packed_info & TAB_INFO_USING_INDEX) - extra.append(STRING_WITH_LEN("; Using index")); + eta->push_extra(ET_USING_INDEX); if (tab->packed_info & TAB_INFO_USING_WHERE) - extra.append(STRING_WITH_LEN("; Using where")); + eta->push_extra(ET_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)); + eta->push_extra(ET_FULL_SCAN_ON_NULL_KEY); } else { @@ -22615,30 +23260,26 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && table->file->pushed_idx_cond) - extra.append(STRING_WITH_LEN("; Using index condition")); + eta->push_extra(ET_USING_INDEX_CONDITION); else if (tab->cache_idx_cond) - extra.append(STRING_WITH_LEN("; Using index condition(BKA)")); + eta->push_extra(ET_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); + eta->push_extra(ET_USING); } 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(')'); + eta->push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD); + eta->range_checked_map= tab->keys; } - else if (tab->select->cond) + else if (tab->select->cond || + (tab->cache_select && tab->cache_select->cond)) { const COND *pushed_cond= tab->table->file->pushed_cond; @@ -22648,16 +23289,19 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, HA_MUST_USE_TABLE_CONDITION_PUSHDOWN)) && pushed_cond) { - extra.append(STRING_WITH_LEN("; Using where with pushed " - "condition")); + eta->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION); + /* + psergey-todo: what to do? This was useful with NDB only. + if (explain_flags & DESCRIBE_EXTENDED) { extra.append(STRING_WITH_LEN(": ")); ((COND *)pushed_cond)->print(&extra, QT_ORDINARY); } + */ } else - extra.append(STRING_WITH_LEN("; Using where")); + eta->push_extra(ET_USING_WHERE); } } if (table_list /* SJM bushes don't have table_list */ && @@ -22665,19 +23309,20 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, 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")); + eta->push_extra(ET_SKIP_OPEN_TABLE); else if (table_list->table_open_method == OPEN_FRM_ONLY) - extra.append(STRING_WITH_LEN("; Open_frm_only")); + eta->push_extra(ET_OPEN_FRM_ONLY); else - extra.append(STRING_WITH_LEN("; Open_full_table")); + eta->push_extra(ET_OPEN_FULL_TABLE); + /* psergey-note: the following has a bug.*/ if (table_list->has_db_lookup_value && table_list->has_table_lookup_value) - extra.append(STRING_WITH_LEN("; Scanned 0 databases")); + eta->push_extra(ET_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")); + eta->push_extra(ET_SCANNED_1_DATABASE); else - extra.append(STRING_WITH_LEN("; Scanned all databases")); + eta->push_extra(ET_SCANNED_ALL_DATABASES); } if (key_read) { @@ -22685,69 +23330,52 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, { 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); + eta->push_extra(ET_USING_INDEX_FOR_GROUP_BY); + eta->loose_scan_is_scanning= qgs->loose_scan_is_scanning(); } else - extra.append(STRING_WITH_LEN("; Using index")); + eta->push_extra(ET_USING_INDEX); } if (table->reginfo.not_exists_optimize) - extra.append(STRING_WITH_LEN("; Not exists")); + eta->push_extra(ET_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); - } + explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), + &eta->mrr_type); + if (eta->mrr_type.length() > 0) + eta->push_extra(ET_USING_MRR); } if (need_tmp_table) { need_tmp_table=0; - extra.append(STRING_WITH_LEN("; Using temporary")); + xpl_sel->using_temporary= true; } if (need_order) { need_order=0; - extra.append(STRING_WITH_LEN("; Using filesort")); + xpl_sel->using_filesort= true; } if (distinct & test_all_bits(used_tables, join->select_list_used_tables)) - extra.append(STRING_WITH_LEN("; Distinct")); + eta->push_extra(ET_DISTINCT); if (tab->loosescan_match_tab) { - extra.append(STRING_WITH_LEN("; LooseScan")); + eta->push_extra(ET_LOOSESCAN); } if (tab->first_weedout_table) - extra.append(STRING_WITH_LEN("; Start temporary")); + eta->push_extra(ET_START_TEMPORARY); if (tab->check_weed_out_table) - extra.append(STRING_WITH_LEN("; End temporary")); + eta->push_extra(ET_END_TEMPORARY); else if (tab->do_firstmatch) { if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1) - extra.append(STRING_WITH_LEN("; FirstMatch")); + eta->push_extra(ET_FIRST_MATCH); else { - extra.append(STRING_WITH_LEN("; FirstMatch(")); + eta->push_extra(ET_FIRST_MATCH); TABLE *prev_table=tab->do_firstmatch->table; if (prev_table->derived_select_number) { @@ -22756,11 +23384,10 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, int len= my_snprintf(namebuf, sizeof(namebuf)-1, "<derived%u>", prev_table->derived_select_number); - extra.append(namebuf, len); + eta->firstmatch_table_name.append(namebuf, len); } else - extra.append(prev_table->pos_in_table_list->alias); - extra.append(STRING_WITH_LEN(")")); + eta->firstmatch_table_name.append(prev_table->pos_in_table_list->alias); } } @@ -22768,26 +23395,16 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, { if (tab->ref.cond_guards[part]) { - extra.append(STRING_WITH_LEN("; Full scan on NULL key")); + eta->push_extra(ET_FULL_SCAN_ON_NULL_KEY); break; } } if (tab->cache) { - extra.append(STRING_WITH_LEN("; Using join buffer")); - tab->cache->print_explain_comment(&extra); - } - - /* Skip initial "; "*/ - const char *str= extra.ptr(); - uint32 len= extra.length(); - if (len) - { - str += 2; - len -= 2; + eta->push_extra(ET_USING_JOIN_BUFFER); + tab->cache->save_explain_data(&eta->bka_type); } - item_list.push_back(new Item_string(str, len, cs)); } if (saved_join_tab) @@ -22795,16 +23412,49 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, // For next iteration used_tables|=table->map; - if (result->send_data(item_list)) - error= 1; + } + output->add_node(xpl_sel); + } + + for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); + unit; + unit= 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 (!(unit->item && unit->item->eliminated) && // (1) + (!unit->derived || unit->derived->is_materialized_derived())) // (2) + { + explain_node->add_child(unit->first_select()->select_number); } } + + if (!error && select_lex->is_top_level_node()) + output->query_plan_ready(); + + DBUG_RETURN(error); } /* - See st_select_lex::print_explain() for the SHOW EXPLAIN counterpart + 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, @@ -22813,10 +23463,15 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, THD *thd=join->thd; select_result *result=join->result; DBUG_ENTER("select_describe"); - join->error= join->print_explain(result, thd->lex->describe, - FALSE, /* Not on-the-fly */ - need_tmp_table, need_order, distinct, - message); + + /* 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; @@ -22866,7 +23521,7 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) if (unit->is_union()) { - unit->fake_select_lex->select_number= UINT_MAX; // jost for initialization + unit->fake_select_lex->select_number= FAKE_SELECT_LEX_ID; // jost 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))) @@ -23927,6 +24582,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) @@ -23944,7 +24601,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) { @@ -23986,6 +24644,7 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, { select->set_quick(reverse_quick); *need_sort= FALSE; + *scanned_limit= select->quick->records; return select->quick->index; } else @@ -24014,6 +24673,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; } diff --git a/sql/sql_select.h b/sql/sql_select.h index 9ff08a3bc03..c413d0ca023 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -189,6 +189,12 @@ 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; @@ -243,7 +249,8 @@ 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; + /* Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra' column, or 0 if there is no info. @@ -310,8 +317,9 @@ typedef struct st_join_table { uint used_null_fields; uint used_uneven_bit_fields; enum join_type type; - bool cached_eq_ref_table,eq_ref_table,not_used_in_distinct; - bool sorted; + bool cached_eq_ref_table,eq_ref_table; + bool shortcut_for_distinct; + bool sorted; /* If it's not 0 the number stored this field indicates that the index scan has been chosen to access the table data and we expect to scan @@ -527,6 +535,7 @@ typedef struct st_join_table { !(used_sjm_lookup_tables & ~emb_sj_nest->sj_inner_tables)); } + void remove_redundant_bnl_scan_conds(); } JOIN_TAB; @@ -1164,6 +1173,11 @@ public: bool cleaned; DYNAMIC_ARRAY keyuse; Item::cond_result cond_value, having_value; + /** + Impossible where after reading const tables + (set in make_join_statistics()) + */ + bool impossible_where; List<Item> all_fields; ///< to store all fields that used in query ///Above list changed to use temporary table List<Item> tmp_all_fields1, tmp_all_fields2, tmp_all_fields3; @@ -1319,6 +1333,8 @@ public: pre_sort_join_tab= NULL; emb_sjm_nest= NULL; sjm_lookup_tables= 0; + + exec_saved_explain= false; /* 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 @@ -1327,9 +1343,16 @@ public: table_access_tabs= NULL; } + /* + TRUE <=> There was a JOIN::exec() call, which saved this JOIN's EXPLAIN. + The idea is that we also save at the end of JOIN::optimize(), but that + might not be the final plan. + */ + bool exec_saved_explain; + int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num, - COND *conds, uint og_num, ORDER *order, ORDER *group, - Item *having, ORDER *proc_param, SELECT_LEX *select, + COND *conds, uint og_num, ORDER *order, bool skip_order_by, + ORDER *group, Item *having, ORDER *proc_param, SELECT_LEX *select, SELECT_LEX_UNIT *unit); bool prepare_stage2(); int optimize(); @@ -1451,11 +1474,11 @@ public: { return (unit->item && unit->item->is_in_predicate()); } - - int print_explain(select_result_sink *result, uint8 explain_flags, - bool on_the_fly, - bool need_tmp_table, bool need_order, - bool distinct,const char *message); + 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); private: /** TRUE if the query contains an aggregate function but has no GROUP @@ -1811,11 +1834,14 @@ 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, Item **const_item= NULL); +bool cond_is_datetime_is_null(Item *cond); +bool cond_has_datetime_is_null(Item *cond); /* Table elimination entry point function */ void eliminate_tables(JOIN *join); @@ -1825,6 +1851,29 @@ 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, + 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 print_explain_row(select_result_sink *result, + uint8 options, + uint select_number, + const char *select_type, + const char *table_name, + const char *partitions, + enum join_type jtype, + const char *possible_keys, + const char *index, + const char *key_len, + const char *ref, + ha_rows *rows, + const char *extra); +void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line); + /**************************************************************************** Temporary table support for SQL Runtime ***************************************************************************/ @@ -1837,7 +1886,8 @@ void push_index_cond(JOIN_TAB *tab, uint keyno); TABLE *create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields, ORDER *group, bool distinct, bool save_sum_fields, ulonglong select_options, ha_rows rows_limit, - const char* alias, bool do_not_open=FALSE); + const char* alias, bool do_not_open=FALSE, + bool keep_row_order= FALSE); void free_tmp_table(THD *thd, TABLE *entry); bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table, TMP_ENGINE_COLUMNDEF *start_recinfo, diff --git a/sql/sql_show.cc b/sql/sql_show.cc index d63df895a13..bcdffce71f1 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -94,6 +94,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", @@ -353,7 +355,7 @@ bool mysqld_show_authors(THD *thd) 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 Item_empty_string("Comment",512)); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) @@ -387,7 +389,7 @@ bool mysqld_show_contributors(THD *thd) 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 Item_empty_string("Comment", 512)); if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) @@ -2081,8 +2083,11 @@ 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[0]) + { + buffer->append('@'); + append_identifier(thd, buffer, definer_host->str, definer_host->length); + } buffer->append(' '); } @@ -2364,8 +2369,8 @@ void Show_explain_request::call_in_target_thread() DBUG_ASSERT(current_thd == target_thd); set_current_thd(request_thd); - if (target_thd->lex->unit.print_explain(explain_buf, 0 /* explain flags*/, - &printed_anything)) + if (target_thd->lex->print_explain(explain_buf, 0 /* explain flags*/, + &printed_anything)) { failed_to_produce= TRUE; } @@ -2396,6 +2401,86 @@ int select_result_explain_buffer::send_data(List<Item> &items) DBUG_RETURN(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 */); + return send_data(fields); +} + + +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))) + return true; + rows.push_back(row); + + while ((item= it++)) + { + DBUG_ASSERT(column < n_columns); + StringBuffer<32> buf; + const char *data_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(); + } + } + + char *ptr= (char*)thd->alloc(data_len + 1); + memcpy(ptr, data_ptr, data_len + 1); + 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. @@ -2580,7 +2665,8 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) 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 < unow.val ? unow.val - start_utime : 0; + ulonglong utime= start_utime && start_utime < unow.val + ? unow.val - start_utime : 0; table->field[5]->store(utime / HRTIME_RESOLUTION, TRUE); /* STATE */ if ((val= thread_state_info(tmp))) @@ -2632,6 +2718,9 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) 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); + if (schema_table_store_record(thd, table)) { mysql_mutex_unlock(&LOCK_thread_count); @@ -2784,19 +2873,20 @@ void remove_status_vars(SHOW_VAR *list) for (; list->name; list++) { - int res= 0, a= 0, b= all_status_vars.elements, c= (a+b)/2; - for (; b-a > 0; c= (a+b)/2) + int first= 0, last= ((int) all_status_vars.elements) - 1; + for ( ; first <= last; ) { - res= show_var_cmp(list, all+c); - if (res < 0) - b= c; + int res, middle= (first + last) / 2; + if ((res= show_var_cmp(list, all + middle)) < 0) + last= middle - 1; else if (res > 0) - a= c; + first= middle + 1; else + { + all[middle].type= SHOW_UNDEF; break; + } } - if (res == 0) - all[c].type= SHOW_UNDEF; } shrink_var_array(&all_status_vars); mysql_mutex_unlock(&LOCK_status); @@ -8320,6 +8410,22 @@ 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}, + {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}, @@ -8473,7 +8579,7 @@ ST_FIELD_INFO view_fields_info[]= 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}, @@ -8483,7 +8589,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}, @@ -8494,7 +8600,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}, @@ -8506,7 +8612,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}, @@ -8691,6 +8797,7 @@ ST_FIELD_INFO processlist_fields_info[]= 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}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} }; @@ -8915,6 +9022,10 @@ ST_FIELD_INFO show_explain_fields_info[]= ST_SCHEMA_TABLE schema_tables[]= { + {"ALL_PLUGINS", plugin_fields_info, create_schema_table, + fill_all_plugins, make_old_format, 0, 5, -1, 0, 0}, + {"APPLICABLE_ROLES", applicable_roles_fields_info, create_schema_table, + fill_schema_applicable_roles, 0, 0, -1, -1, 0, 0}, {"CHARACTER_SETS", charsets_fields_info, create_schema_table, fill_schema_charsets, make_character_sets_old_format, 0, -1, -1, 0, 0}, {"CLIENT_STATISTICS", client_stats_fields_info, create_schema_table, @@ -8928,6 +9039,8 @@ ST_SCHEMA_TABLE schema_tables[]= OPTIMIZE_I_S_TABLE|OPEN_VIEW_FULL}, {"COLUMN_PRIVILEGES", column_privileges_fields_info, create_schema_table, fill_schema_column_privileges, 0, 0, -1, -1, 0, 0}, + {"ENABLED_ROLES", enabled_roles_fields_info, create_schema_table, + fill_schema_enabled_roles, 0, 0, -1, -1, 0, 0}, {"ENGINES", engines_fields_info, create_schema_table, fill_schema_engines, make_old_format, 0, -1, -1, 0, 0}, #ifdef HAVE_EVENT_SCHEDULER @@ -8961,8 +9074,6 @@ ST_SCHEMA_TABLE schema_tables[]= OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, {"PLUGINS", plugin_fields_info, create_schema_table, fill_plugins, make_old_format, 0, -1, -1, 0, 0}, - {"ALL_PLUGINS", plugin_fields_info, create_schema_table, - fill_all_plugins, make_old_format, 0, 5, -1, 0, 0}, {"PROCESSLIST", processlist_fields_info, create_schema_table, fill_schema_processlist, make_old_format, 0, -1, -1, 0, 0}, {"PROFILING", query_profile_statistics_info, create_schema_table, diff --git a/sql/sql_show.h b/sql/sql_show.h index ec4d6a2b7c9..10276e8b65e 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -121,7 +121,7 @@ enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table); /* 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(ulong id); +THD *find_thread_by_id(longlong id, bool query_id= false); class select_result_explain_buffer; /* diff --git a/sql/sql_string.h b/sql/sql_string.h index 352dfbe9fa3..7687490c8aa 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -164,6 +164,11 @@ public: LEX_STRING lex_string = { (char*) ptr(), length() }; return lex_string; } + LEX_CSTRING lex_cstring() const + { + LEX_CSTRING lex_cstring = { ptr(), length() }; + return lex_cstring; + } void set(String &str,uint32 offset,uint32 arg_length) { @@ -515,6 +520,38 @@ public: } }; + +// 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(const CHARSET_INFO *cs) : String(buff, buff_sz, cs) + { + length(0); + } + StringBuffer(const char *str, size_t length, const CHARSET_INFO *cs) + : String(buff, buff_sz, cs) + { + set(str, length, cs); + } +}; + + static inline bool check_if_only_end_space(CHARSET_INFO *cs, const char *str, const char *end) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index d113ea85a26..df499797797 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -369,11 +369,8 @@ 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; @@ -386,7 +383,7 @@ uint filename_to_tablename(const char *from, char *to, uint to_length { res= (strxnmov(to, to_length, MYSQL50_TABLE_NAME_PREFIX, from, NullS) - to); - if (IF_DBUG(!stay_quiet,0)) + if (!stay_quiet) sql_print_error("Invalid (old?) table or database name '%s'", from); } @@ -3042,6 +3039,90 @@ void promote_first_timestamp_column(List<Create_field> *column_definitions) } +/** + 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(ER_DUP_INDEX), + key_info->name, + thd->lex->query_tables->db, + thd->lex->query_tables->table_name); + break; + } + } +} + + /* Preparation for table creation @@ -3958,8 +4039,12 @@ 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)) { @@ -4494,7 +4579,7 @@ bool create_table_impl(THD *thd, if (!hton->discover_table_structure) { - my_error(ER_ILLEGAL_HA, MYF(0), hton_name(hton)->str, db, table_name); + my_error(ER_TABLE_MUST_HAVE_COLUMNS, MYF(0)); goto err; } @@ -4757,6 +4842,7 @@ make_unique_key_name(const char *field_name,KEY *start,KEY *end) 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 @@ -4774,11 +4860,16 @@ mysql_rename_table(handlerton *base, const char *old_db, char tmp_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_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)); @@ -4853,6 +4944,9 @@ mysql_rename_table(handlerton *base, const char *old_db, old_name, strlen(old_name)); } + // Restore options bits to the original value + thd->variables.option_bits= save_bits; + DBUG_RETURN(error != 0); } @@ -5276,7 +5370,7 @@ handle_if_exists_options(THD *thd, TABLE *table, Alter_info *alter_info) for (f_ptr=table->field; *f_ptr; f_ptr++) { if (my_strcasecmp(system_charset_info, - sql_field->field_name, (*f_ptr)->field_name) == 0) + sql_field->change, (*f_ptr)->field_name) == 0) { break; } @@ -5356,17 +5450,29 @@ handle_if_exists_options(THD *thd, TABLE *table, Alter_info *alter_info) Key *key; List_iterator<Key> key_it(alter_info->key_list); uint n_key; + const char *keyname; while ((key=key_it++)) { if (!key->create_if_not_exists) continue; + /* 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) + { + 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; + } for (n_key=0; n_key < table->s->keys; n_key++) { if (my_strcasecmp(system_charset_info, - key->name.str, table->key_info[n_key].name) == 0) + keyname, table->key_info[n_key].name) == 0) { push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, - ER_DUP_KEYNAME, ER(ER_DUP_KEYNAME), key->name.str); + ER_DUP_KEYNAME, ER(ER_DUP_KEYNAME), keyname); key_it.remove(); if (key->type == Key::FOREIGN_KEY) { @@ -5799,6 +5905,10 @@ static bool fill_alter_inplace_info(THD *thd, new_key->user_defined_key_parts)) 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. @@ -6579,7 +6689,7 @@ static bool mysql_inplace_alter_table(THD *thd, */ (void) mysql_rename_table(db_type, alter_ctx->new_db, alter_ctx->new_alias, - alter_ctx->db, alter_ctx->alias, 0); + alter_ctx->db, alter_ctx->alias, NO_FK_CHECKS); DBUG_RETURN(true); } rename_table_in_stat_tables(thd, alter_ctx->db,alter_ctx->alias, @@ -6703,7 +6813,7 @@ 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; @@ -6711,6 +6821,14 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, 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)) @@ -7067,6 +7185,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) @@ -7564,7 +7688,8 @@ simple_rename_or_index_change(THD *thd, TABLE_LIST *table_list, { (void) mysql_rename_table(old_db_type, alter_ctx->new_db, alter_ctx->new_alias, - alter_ctx->db, alter_ctx->table_name, 0); + alter_ctx->db, alter_ctx->table_name, + NO_FK_CHECKS); error= -1; } } @@ -7699,8 +7824,11 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, DEBUG_SYNC(thd, "alter_table_before_open_tables"); 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"); @@ -7891,7 +8019,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if (alter_info->flags == 0) { my_snprintf(alter_ctx.tmp_name, sizeof(alter_ctx.tmp_name), - ER(ER_INSERT_INFO), 0L, 0L, 0L); + ER(ER_INSERT_INFO), 0L, 0L, + thd->get_stmt_da()->current_statement_warn_count()); my_ok(thd, 0L, 0L, alter_ctx.tmp_name); DBUG_RETURN(false); } @@ -8107,11 +8236,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, { Alter_inplace_info ha_alter_info(create_info, alter_info, key_info, key_count, -#ifdef WITH_PARTITION_STORAGE_ENGINE - thd->work_part_info, -#else - NULL, -#endif + IF_PARTITIONING(thd->work_part_info, NULL), ignore); TABLE *altered_table= NULL; bool use_inplace= true; @@ -8232,15 +8357,14 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, 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 (mysql_inplace_alter_table(thd, table_list, table, - altered_table, - &ha_alter_info, - inplace_supported, &target_mdl_request, - &alter_ctx)) - { + + if (res) DBUG_RETURN(true); - } goto end_inplace; } @@ -8468,9 +8592,11 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, // 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); + alter_ctx.db, alter_ctx.alias, + FN_FROM_IS_TMP | NO_FK_CHECKS); goto err_with_mdl; } @@ -8489,7 +8615,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, 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); + alter_ctx.db, alter_ctx.alias, + FN_FROM_IS_TMP | NO_FK_CHECKS); goto err_with_mdl; } rename_table_in_stat_tables(thd, alter_ctx.db,alter_ctx.alias, @@ -8867,6 +8994,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, { /* Not a duplicate key error. */ to->file->print_error(error, MYF(0)); + error= 1; break; } else diff --git a/sql/sql_table.h b/sql/sql_table.h index 6bd111cae6d..c42f8aaa39e 100644 --- a/sql/sql_table.h +++ b/sql/sql_table.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. +/* Copyright (c) 2006, 2013, Oracle and/or its affiliates. Copyright (c) 2011, 2013, Monty Program Ab. This program is free software; you can redistribute it and/or modify @@ -134,12 +134,11 @@ static const uint FRM_ONLY= 1 << 3; 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 */ +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); diff --git a/sql/sql_time.cc b/sql/sql_time.cc index 9611b947aa3..831a7fa2a20 100644 --- a/sql/sql_time.cc +++ b/sql/sql_time.cc @@ -267,7 +267,7 @@ to_ascii(CHARSET_INFO *cs, /* Character set-aware version of str_to_time() */ bool str_to_time(CHARSET_INFO *cs, const char *str,uint length, - MYSQL_TIME *l_time, ulonglong fuzzydate, MYSQL_TIME_STATUS *status) + MYSQL_TIME *l_time, ulonglong fuzzydate, MYSQL_TIME_STATUS *status) { char cnv[32]; if ((cs->state & MY_CS_NONASCII) != 0) diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 939541f913e..682528f9766 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -434,8 +434,14 @@ 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. */ +#ifdef WITH_WSREP + if (!trust_function_creators && + (WSREP_EMULATE_BINLOG(thd) || mysql_bin_log.is_open()) && + !(thd->security_ctx->master_access & SUPER_ACL)) +#else if (!trust_function_creators && mysql_bin_log.is_open() && !(thd->security_ctx->master_access & SUPER_ACL)) +#endif /* WITH_WSREP */ { my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, MYF(0)); DBUG_RETURN(TRUE); @@ -663,46 +669,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, 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; - } - } - - /* - 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 1; /* Let us check if all references to fields in old/new versions of row in @@ -794,29 +762,14 @@ 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, - Sql_condition::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) + if (lex->sphead->m_chistics->suid != SP_IS_NOT_SUID) { /* 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; + lex->definer->set_lex_string(trg_definer, trg_definer_holder); } else { @@ -854,7 +807,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, stmt_query->append(STRING_WITH_LEN("CREATE ")); - if (trg_definer) + if (lex->sphead->m_chistics->suid != SP_IS_NOT_SUID) { /* Append definer-clause if the trigger is SUID (a usual trigger in @@ -2484,7 +2437,7 @@ bool load_table_name_for_trigger(THD *thd, DBUG_RETURN(FALSE); } #ifdef WITH_WSREP -int wsrep_create_trigger_query(THD *thd, uchar** buf, int* buf_len) +int wsrep_create_trigger_query(THD *thd, uchar** buf, size_t* buf_len) { LEX *lex= thd->lex; String stmt_query; @@ -2496,7 +2449,7 @@ int wsrep_create_trigger_query(THD *thd, uchar** buf, int* buf_len) { if (!thd->slave_thread) { - if (!(lex->definer= create_default_definer(thd))) + if (!(lex->definer= create_default_definer(thd, false))) return 1; } } diff --git a/sql/sql_union.cc b/sql/sql_union.cc index f7168ffb7ab..b046b3a4de8 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -129,6 +129,7 @@ bool select_union::flush() table_alias name of the temporary table bit_fields_as_long convert bit fields to ulonglong create_table whether to physically create result table + keep_row_order keep rows in order as they were inserted DESCRIPTION Create a temporary table that is used to store the result of a UNION, @@ -143,7 +144,8 @@ bool select_union::create_result_table(THD *thd_arg, List<Item> *column_types, bool is_union_distinct, ulonglong options, const char *alias, - bool bit_fields_as_long, bool create_table) + bool bit_fields_as_long, bool create_table, + bool keep_row_order) { DBUG_ASSERT(table == 0); tmp_table_param.init(); @@ -153,7 +155,7 @@ select_union::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, alias, - !create_table))) + !create_table, keep_row_order))) return TRUE; table->keys_in_use_for_query.clear_all(); @@ -325,6 +327,7 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, sl->group_list.elements, can_skip_order_by ? NULL : sl->order_list.first, + can_skip_order_by, sl->group_list.first, sl->having, (is_union_select ? NULL : @@ -508,7 +511,7 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, 0, 0, global_parameters->order_list.elements, // og_num global_parameters->order_list.first, // order - NULL, NULL, NULL, + false, NULL, NULL, NULL, fake_select_lex, this); fake_select_lex->table_list.empty(); } @@ -620,6 +623,7 @@ bool st_select_lex_unit::exec() ulonglong add_rows=0; ha_rows examined_rows= 0; DBUG_ENTER("st_select_lex_unit::exec"); + bool was_executed= executed; if (executed && !uncacheable && !describe) DBUG_RETURN(FALSE); @@ -628,6 +632,11 @@ bool st_select_lex_unit::exec() item->make_const(); 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 (uncacheable || !item || !item->assigned() || describe) { @@ -775,6 +784,9 @@ bool st_select_lex_unit::exec() */ if (!fake_select_lex->ref_pointer_array) 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, diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 34301e2234f..b85f5b90cd7 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -260,7 +260,7 @@ int mysql_update(THD *thd, 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 @@ -270,14 +270,18 @@ 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); + 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); @@ -310,10 +314,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= 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 : @@ -370,7 +378,12 @@ int mysql_update(THD *thd, Item::cond_result cond_value; conds= remove_eq_conds(thd, conds, &cond_value); if (cond_value == Item::COND_FALSE) + { limit= 0; // Impossible WHERE + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + } } /* @@ -388,6 +401,11 @@ 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) + goto exit_without_my_ok; + my_ok(thd); // No matching records DBUG_RETURN(0); } @@ -400,6 +418,10 @@ int mysql_update(THD *thd, if (error || !limit || thd->is_error() || (select && select->check_quick(thd, safe_update, limit))) { + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + delete select; free_underlaid_joins(thd, select_lex); /* @@ -434,20 +456,25 @@ 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 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)); } @@ -455,18 +482,47 @@ int mysql_update(THD *thd, { if (need_sort) { // Assign table scan index to check below for modified key fields: - used_index= table->file->key_used_on_scan; + query_plan.index= table->file->key_used_on_scan; } - if (used_index != MAX_KEY) + 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, used_index, table->write_set); + 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 || used_key_is_modified)) + 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 exit_without_my_ok; + query_plan.save_explain_data(thd->lex->explain); + + DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", + dbug_serve_apcs(thd, 1);); + + 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! @@ -474,13 +530,13 @@ int mysql_update(THD *thd, MY_BITMAP *save_read_set= table->read_set; MY_BITMAP *save_write_set= table->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 @@ -535,22 +591,22 @@ int mysql_update(THD *thd, /* 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 */ - if (used_index == MAX_KEY || (select && select->quick)) + if (query_plan.index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 0, 1, FALSE)) goto err; } else - init_read_record_idx(&info, thd, table, 1, used_index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); THD_STAGE_INFO(thd, stage_searching_rows_for_update); ha_rows tmp_limit= limit; @@ -616,6 +672,7 @@ int mysql_update(THD *thd, select= new SQL_SELECT; select->head=table; } + //psergey-todo: disable SHOW EXPLAIN because the plan was deleted? if (reinit_io_cache(&tempfile,READ_CACHE,0L,0,0)) error=1; /* purecov: inspected */ select->file=tempfile; // Read row ptrs from this file @@ -970,11 +1027,21 @@ int mysql_update(THD *thd, DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); err: + delete select; free_underlaid_joins(thd, select_lex); table->disable_keyread(); thd->abort_on_warning= 0; DBUG_RETURN(1); + +exit_without_my_ok: + query_plan.save_explain_data(thd->lex->explain); + + int err2= thd->lex->explain->send_explain(thd); + + delete select; + free_underlaid_joins(thd, select_lex); + DBUG_RETURN((err2 || thd->is_error()) ? 1 : 0); } /* @@ -1395,17 +1462,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))) + &thd->lex->select_lex.leaf_tables, + fields, values, + handle_duplicates, ignore))) { DBUG_RETURN(TRUE); } thd->abort_on_warning= thd->is_strict_mode(); - List<Item> total_list; res= mysql_select(thd, &select_lex->ref_pointer_array, @@ -1421,6 +1487,11 @@ bool mysql_multi_update(THD *thd, res|= thd->is_error(); if (unlikely(res)) (*result)->abort_result_set(); + else + { + if (thd->lex->describe) + res= thd->lex->explain->send_explain(thd); + } thd->abort_on_warning= 0; DBUG_RETURN(res); } @@ -1435,7 +1506,7 @@ multi_update::multi_update(TABLE_LIST *table_list, 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) {} @@ -1458,6 +1529,10 @@ 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_STAGE_INFO(thd, stage_updating_main_table); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index e0a567420ba..505b8b25f89 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -39,8 +39,7 @@ const LEX_STRING view_type= { C_STRING_WITH_LEN("VIEW") }; -static int mysql_register_view(THD *thd, TABLE_LIST *view, - enum_view_create_mode mode); +static int mysql_register_view(THD *, TABLE_LIST *, enum_view_create_mode); /* Make a unique name for an anonymous view column @@ -467,60 +466,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, Sql_condition::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). @@ -667,6 +615,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 (mysql_bin_log.is_open()) { String buff; @@ -875,7 +832,11 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, 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->calc_md5(md5); if (!(view->md5.str= (char*) thd->memdup(md5, 32))) { @@ -1070,19 +1031,16 @@ err: bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, uint flags) { - 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->view) { /* @@ -1169,8 +1127,16 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_VIEW_FRM_NO_USER, ER(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 @@ -1294,15 +1260,14 @@ 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. 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)) { /* The user we run EXPLAIN as (either the connected user who issued @@ -1470,6 +1435,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) { /* @@ -1490,6 +1456,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) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 50b30d94f53..7f39720ab56 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -23,21 +23,14 @@ */ %{ -/* thd is passed as an argument to yyparse(), and subsequently to yylex(). -** The type will be void*, so it must be cast to (THD*) when used. -** Use the YYTHD macro for this. -*/ -#define YYPARSE_PARAM yythd -#define YYLEX_PARAM yythd -#define YYTHD ((THD *)yythd) -#define YYLIP (& YYTHD->m_parser_state->m_lip) -#define YYPS (& YYTHD->m_parser_state->m_yacc) -#define YYCSCL YYTHD->variables.character_set_client +#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 #define YYMAXDEPTH 3200 /* Because of 64K stack */ -#define Lex (YYTHD->lex) +#define Lex (thd->lex) #define Select Lex->current_select #include "sql_priv.h" @@ -83,7 +76,7 @@ int yylex(void *yylval, void *yythd); ulong val= *(F); \ if (my_yyoverflow((B), (D), &val)) \ { \ - yyerror((char*) (A)); \ + yyerror(current_thd, (char*) (A)); \ return 2; \ } \ else \ @@ -95,7 +88,7 @@ int yylex(void *yylval, void *yythd); #define MYSQL_YYABORT \ do \ { \ - LEX::cleanup_lex_after_parse_error(YYTHD);\ + LEX::cleanup_lex_after_parse_error(thd); \ YYABORT; \ } while (0) @@ -181,10 +174,8 @@ void my_parse_error(const char *s) to abort from the parser. */ -void MYSQLerror(const char *s) +void MYSQLerror(THD *thd, const char *s) { - THD *thd= current_thd; - /* Restore the original LEX if it was replaced when parsing a stored procedure. We must ensure that a parsing error @@ -961,11 +952,13 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %} %pure_parser /* We have threads */ +%parse-param { THD *thd } +%lex-param { THD *thd } /* - Currently there are 185 shift/reduce conflicts. + Currently there are 163 shift/reduce conflicts. We should not introduce new conflicts any more. */ -%expect 185 +%expect 163 /* Comments for TOKENS. @@ -979,7 +972,7 @@ 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. */ @@ -988,6 +981,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %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 @@ -1090,6 +1084,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %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 */ @@ -1208,6 +1203,7 @@ 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 @@ -1441,10 +1437,13 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %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 */ @@ -1600,6 +1599,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 @@ -1691,6 +1691,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 @@ -1766,7 +1770,8 @@ 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 %type <charset> opt_collate @@ -1797,7 +1802,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); 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 + 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 @@ -1820,7 +1825,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); 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 clear_privileges flush_options flush_option opt_with_read_lock flush_options_list equal optional_braces @@ -1853,6 +1859,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); definer_opt no_definer definer get_diagnostics parse_vcol_expr vcol_opt_specifier vcol_opt_attribute vcol_opt_attribute_list vcol_attribute + explainable_command END_OF_INPUT %type <NONE> call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt @@ -1893,6 +1900,8 @@ END_OF_INPUT %type <is_not_empty> opt_union_order_or_limit +%type <NONE> ROLE_SYM + %% @@ -1920,7 +1929,6 @@ rule: <-- starts at col 1 query: END_OF_INPUT { - THD *thd= YYTHD; if (!thd->bootstrap && (!(thd->lex->select_lex.options & OPTION_FOUND_COMMENT))) { @@ -1934,7 +1942,7 @@ query: { Lex_input_stream *lip = YYLIP; - if ((YYTHD->client_capabilities & CLIENT_MULTI_QUERIES) && + if ((thd->client_capabilities & CLIENT_MULTI_QUERIES) && lip->multi_statements && ! lip->eof()) { @@ -2034,7 +2042,6 @@ statement: deallocate: deallocate_or_drop PREPARE_SYM ident { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->sql_command= SQLCOM_DEALLOCATE_PREPARE; lex->prepared_stmt_name= $3; @@ -2049,7 +2056,6 @@ deallocate_or_drop: prepare: PREPARE_SYM ident FROM prepare_src { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->sql_command= SQLCOM_PREPARE; lex->prepared_stmt_name= $2; @@ -2059,14 +2065,12 @@ prepare: prepare_src: TEXT_STRING_sys { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->prepared_stmt_code= $1; lex->prepared_stmt_code_is_varref= FALSE; } | '@' ident_or_text { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->prepared_stmt_code= $2; lex->prepared_stmt_code_is_varref= TRUE; @@ -2076,7 +2080,6 @@ prepare_src: execute: EXECUTE_SYM ident { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->sql_command= SQLCOM_EXECUTE; lex->prepared_stmt_name= $2; @@ -2152,7 +2155,6 @@ master_def: | MASTER_PASSWORD_SYM EQ TEXT_STRING_sys { Lex->mi.password = $3.str; - Lex->contains_plaintext_password= true; } | MASTER_PORT_SYM EQ ulong_num { @@ -2215,7 +2217,7 @@ master_def: } if (Lex->mi.heartbeat_period > slave_net_timeout) { - push_warning_printf(YYTHD, Sql_condition::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)); } @@ -2223,7 +2225,7 @@ master_def: { if (Lex->mi.heartbeat_period != 0.0) { - push_warning_printf(YYTHD, Sql_condition::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)); Lex->mi.heartbeat_period= 0.0; @@ -2317,7 +2319,6 @@ master_file_def: optional_connection_name: /* empty */ { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->mi.connection_name= thd->variables.default_master_connection; } @@ -2342,7 +2343,6 @@ connection_name: create: CREATE opt_table_options TABLE_SYM opt_if_not_exists table_ident { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->sql_command= SQLCOM_CREATE_TABLE; if (!lex->select_lex.add_table_to_list(thd, $5, NULL, @@ -2367,13 +2367,13 @@ create: } create_body { - LEX *lex= YYTHD->lex; + 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(YYTHD); - push_warning_printf(YYTHD, Sql_condition::WARN_LEVEL_WARN, + lex->create_info.db_type= ha_default_handlerton(thd); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_USING_OTHER_HANDLER, ER(ER_WARN_USING_OTHER_HANDLER), hton_name(lex->create_info.db_type)->str, @@ -2440,6 +2440,10 @@ create: { Lex->sql_command = SQLCOM_CREATE_USER; } + | CREATE ROLE_SYM clear_privileges role_list opt_with_admin + { + Lex->sql_command = SQLCOM_CREATE_ROLE; + } | CREATE LOGFILE_SYM GROUP_SYM logfile_group_info { Lex->alter_tablespace_info->ts_cmd_type= CREATE_LOGFILE_GROUP; @@ -2492,7 +2496,6 @@ server_option: | PASSWORD TEXT_STRING_sys { Lex->server_options.password= $2.str; - Lex->contains_plaintext_password= true; } | SOCKET_SYM TEXT_STRING_sys { @@ -2507,7 +2510,6 @@ server_option: event_tail: remember_name EVENT_SYM opt_if_not_exists sp_name { - THD *thd= YYTHD; LEX *lex=Lex; lex->stmt_definition_begin= $1; @@ -2574,7 +2576,7 @@ opt_ev_status: ev_starts: /* empty */ { - Item *item= new (YYTHD->mem_root) Item_func_now_local(0); + Item *item= new (thd->mem_root) Item_func_now_local(0); if (item == NULL) MYSQL_YYABORT; Lex->event_parse_data->item_starts= item; @@ -2624,7 +2626,6 @@ opt_ev_comment: ev_sql_stmt: { - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; @@ -2667,7 +2668,6 @@ ev_sql_stmt: } ev_sql_stmt_inner { - THD *thd= YYTHD; LEX *lex= thd->lex; /* return back to the original memory root ASAP */ @@ -2726,11 +2726,10 @@ sp_name: $$= new sp_name($1, $3, true); if ($$ == NULL) MYSQL_YYABORT; - $$->init_qname(YYTHD); + $$->init_qname(thd); } | ident { - THD *thd= YYTHD; LEX *lex= thd->lex; LEX_STRING db; if (check_routine_name(&$1)) @@ -2800,7 +2799,7 @@ call: lex->sql_command= SQLCOM_CALL; lex->spname= $2; lex->value_list.empty(); - sp_add_used_routine(lex, YYTHD, $2, TYPE_ENUM_PROCEDURE); + sp_add_used_routine(lex, thd, $2, TYPE_ENUM_PROCEDURE); } opt_sp_cparam_list {} ; @@ -2871,12 +2870,12 @@ sp_fdparam: MYSQL_YYABORT; } - sp_variable *spvar= spc->add_variable(YYTHD, + sp_variable *spvar= spc->add_variable(thd, $1, (enum enum_field_types) $3, sp_variable::MODE_IN); - if (lex->sphead->fill_field_definition(YYTHD, lex, + if (lex->sphead->fill_field_definition(thd, lex, (enum enum_field_types) $3, &spvar->field_def)) { @@ -2909,12 +2908,12 @@ sp_pdparam: my_error(ER_SP_DUP_PARAM, MYF(0), $3.str); MYSQL_YYABORT; } - sp_variable *spvar= spc->add_variable(YYTHD, + sp_variable *spvar= spc->add_variable(thd, $3, (enum enum_field_types) $4, (sp_variable::enum_mode) $1); - if (lex->sphead->fill_field_definition(YYTHD, lex, + if (lex->sphead->fill_field_definition(thd, lex, (enum enum_field_types) $4, &spvar->field_def)) { @@ -2977,13 +2976,12 @@ sp_decl: { LEX *lex= Lex; - lex->sphead->reset_lex(YYTHD); + lex->sphead->reset_lex(thd); lex->spcont->declare_var_boundary($2); } type_with_opt_collate sp_opt_default { - THD *thd= YYTHD; LEX *lex= Lex; sp_pcontext *pctx= lex->spcont; uint num_vars= pctx->context_var_count(); @@ -3009,7 +3007,7 @@ sp_decl: spvar->type= var_type; spvar->default_value= dflt_value_item; - if (lex->sphead->fill_field_definition(YYTHD, lex, var_type, + if (lex->sphead->fill_field_definition(thd, lex, var_type, &spvar->field_def)) { MYSQL_YYABORT; @@ -3033,7 +3031,7 @@ sp_decl: } pctx->declare_var_boundary(0); - if (lex->sphead->restore_lex(YYTHD)) + if (lex->sphead->restore_lex(thd)) MYSQL_YYABORT; $$.vars= $2; $$.conds= $$.hndlrs= $$.curs= 0; @@ -3048,14 +3046,13 @@ sp_decl: my_error(ER_SP_DUP_COND, MYF(0), $2.str); MYSQL_YYABORT; } - if(spc->add_condition(YYTHD, $2, $5)) + if(spc->add_condition(thd, $2, $5)) MYSQL_YYABORT; $$.vars= $$.hndlrs= $$.curs= 0; $$.conds= 1; } | DECLARE_SYM sp_handler_type HANDLER_SYM FOR_SYM { - THD *thd= YYTHD; LEX *lex= Lex; sp_head *sp= lex->sphead; @@ -3137,7 +3134,7 @@ sp_decl: sp_cursor_stmt: { - Lex->sphead->reset_lex(YYTHD); + Lex->sphead->reset_lex(thd); } select { @@ -3153,7 +3150,7 @@ sp_cursor_stmt: } lex->sp_lex_in_use= TRUE; $$= lex; - if (lex->sphead->restore_lex(YYTHD)) + if (lex->sphead->restore_lex(thd)) MYSQL_YYABORT; } ; @@ -3201,7 +3198,7 @@ sp_cond: my_error(ER_WRONG_VALUE, MYF(0), "CONDITION", "0"); MYSQL_YYABORT; } - $$= new (YYTHD->mem_root) sp_condition_value($1); + $$= new (thd->mem_root) sp_condition_value($1); if ($$ == NULL) MYSQL_YYABORT; } @@ -3224,7 +3221,7 @@ sqlstate: my_error(ER_SP_BAD_SQLSTATE, MYF(0), $3.str); MYSQL_YYABORT; } - $$= new (YYTHD->mem_root) sp_condition_value($3.str); + $$= new (thd->mem_root) sp_condition_value($3.str); if ($$ == NULL) MYSQL_YYABORT; } @@ -3251,20 +3248,19 @@ sp_hcond: } | SQLWARNING_SYM /* SQLSTATEs 01??? */ { - $$= new (YYTHD->mem_root) sp_condition_value(sp_condition_value::WARNING); + $$= new (thd->mem_root) sp_condition_value(sp_condition_value::WARNING); if ($$ == NULL) MYSQL_YYABORT; } | not FOUND_SYM /* SQLSTATEs 02??? */ { - $$= new (YYTHD->mem_root) sp_condition_value(sp_condition_value::NOT_FOUND); + $$= new (thd->mem_root) sp_condition_value(sp_condition_value::NOT_FOUND); if ($$ == NULL) MYSQL_YYABORT; } | SQLEXCEPTION_SYM /* All other SQLSTATEs */ { - $$= (sp_condition_value *)YYTHD->alloc(sizeof(sp_condition_value)); - $$= new (YYTHD->mem_root) sp_condition_value(sp_condition_value::EXCEPTION); + $$= new (thd->mem_root) sp_condition_value(sp_condition_value::EXCEPTION); if ($$ == NULL) MYSQL_YYABORT; } @@ -3273,7 +3269,6 @@ sp_hcond: signal_stmt: SIGNAL_SYM signal_value opt_set_signal_information { - THD *thd= YYTHD; LEX *lex= thd->lex; Yacc_state *state= & thd->m_parser_state->m_yacc; @@ -3323,7 +3318,7 @@ opt_signal_value: opt_set_signal_information: /* empty */ { - YYTHD->m_parser_state->m_yacc.m_set_signal_info.clear(); + thd->m_parser_state->m_yacc.m_set_signal_info.clear(); } | SET signal_information_item_list ; @@ -3332,7 +3327,7 @@ signal_information_item_list: signal_condition_information_item_name EQ signal_allowed_expr { Set_signal_information *info; - info= & YYTHD->m_parser_state->m_yacc.m_set_signal_info; + info= &thd->m_parser_state->m_yacc.m_set_signal_info; int index= (int) $1; info->clear(); info->m_item[index]= $3; @@ -3341,7 +3336,7 @@ signal_information_item_list: signal_condition_information_item_name EQ signal_allowed_expr { Set_signal_information *info; - info= & YYTHD->m_parser_state->m_yacc.m_set_signal_info; + info= &thd->m_parser_state->m_yacc.m_set_signal_info; int index= (int) $3; if (info->m_item[index] != NULL) { @@ -3412,7 +3407,6 @@ signal_condition_information_item_name: resignal_stmt: RESIGNAL_SYM opt_signal_value opt_set_signal_information { - THD *thd= YYTHD; LEX *lex= thd->lex; Yacc_state *state= & thd->m_parser_state->m_yacc; @@ -3433,7 +3427,7 @@ get_diagnostics: info->set_which_da($2); Lex->sql_command= SQLCOM_GET_DIAGNOSTICS; - Lex->m_sql_cmd= new (YYTHD->mem_root) Sql_cmd_get_diagnostics(info); + Lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_get_diagnostics(info); if (Lex->m_sql_cmd == NULL) MYSQL_YYABORT; @@ -3450,13 +3444,13 @@ which_area: diagnostics_information: statement_information { - $$= new (YYTHD->mem_root) Statement_information($1); + $$= new (thd->mem_root) Statement_information($1); if ($$ == NULL) MYSQL_YYABORT; } | CONDITION_SYM condition_number condition_information { - $$= new (YYTHD->mem_root) Condition_information($2, $3); + $$= new (thd->mem_root) Condition_information($2, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -3465,7 +3459,7 @@ diagnostics_information: statement_information: statement_information_item { - $$= new (YYTHD->mem_root) List<Statement_information_item>; + $$= new (thd->mem_root) List<Statement_information_item>; if ($$ == NULL || $$->push_back($1)) MYSQL_YYABORT; } @@ -3480,7 +3474,7 @@ statement_information: statement_information_item: simple_target_specification EQ statement_information_item_name { - $$= new (YYTHD->mem_root) Statement_information_item($3, $1); + $$= new (thd->mem_root) Statement_information_item($3, $1); if ($$ == NULL) MYSQL_YYABORT; } @@ -3488,8 +3482,8 @@ statement_information_item: simple_target_specification: ident { - Lex_input_stream *lip= &YYTHD->m_parser_state->m_lip; - $$= create_item_for_sp_var(YYTHD, $1, NULL, + 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) @@ -3497,7 +3491,7 @@ simple_target_specification: } | '@' ident_or_text { - $$= new (YYTHD->mem_root) Item_func_get_user_var($2); + $$= new (thd->mem_root) Item_func_get_user_var($2); if ($$ == NULL) MYSQL_YYABORT; } @@ -3522,7 +3516,7 @@ condition_number: condition_information: condition_information_item { - $$= new (YYTHD->mem_root) List<Condition_information_item>; + $$= new (thd->mem_root) List<Condition_information_item>; if ($$ == NULL || $$->push_back($1)) MYSQL_YYABORT; } @@ -3537,7 +3531,7 @@ condition_information: condition_information_item: simple_target_specification EQ condition_information_item_name { - $$= new (YYTHD->mem_root) Condition_information_item($3, $1); + $$= new (thd->mem_root) Condition_information_item($3, $1); if ($$ == NULL) MYSQL_YYABORT; } @@ -3584,7 +3578,7 @@ sp_decl_idents: my_error(ER_SP_DUP_VAR, MYF(0), $1.str); MYSQL_YYABORT; } - spc->add_variable(YYTHD, + spc->add_variable(thd, $1, MYSQL_TYPE_DECIMAL, sp_variable::MODE_IN); @@ -3602,7 +3596,7 @@ sp_decl_idents: my_error(ER_SP_DUP_VAR, MYF(0), $3.str); MYSQL_YYABORT; } - spc->add_variable(YYTHD, + spc->add_variable(thd, $3, MYSQL_TYPE_DECIMAL, sp_variable::MODE_IN); @@ -3640,7 +3634,6 @@ sp_proc_stmt_if: sp_proc_stmt_statement: { - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; @@ -3649,7 +3642,6 @@ sp_proc_stmt_statement: } statement { - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; sp_head *sp= lex->sphead; @@ -3696,7 +3688,7 @@ sp_proc_stmt_statement: sp_proc_stmt_return: RETURN_SYM - { Lex->sphead->reset_lex(YYTHD); } + { Lex->sphead->reset_lex(thd); } expr { LEX *lex= Lex; @@ -3718,7 +3710,7 @@ sp_proc_stmt_return: MYSQL_YYABORT; sp->m_flags|= sp_head::HAS_RETURN; } - if (sp->restore_lex(YYTHD)) + if (sp->restore_lex(thd)) MYSQL_YYABORT; } ; @@ -3727,7 +3719,7 @@ sp_proc_stmt_unlabeled: { /* Unlabeled controls get a secret label. */ LEX *lex= Lex; - lex->spcont->push_label(YYTHD, + lex->spcont->push_label(thd, EMPTY_STR, lex->sphead->instructions()); } @@ -3947,7 +3939,7 @@ sp_fetch_list: ; sp_if: - { Lex->sphead->reset_lex(YYTHD); } + { Lex->sphead->reset_lex(thd); } expr THEN_SYM { LEX *lex= Lex; @@ -3957,11 +3949,11 @@ sp_if: sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, ctx, $2, lex); if (i == NULL || - sp->push_backpatch(i, ctx->push_label(YYTHD, EMPTY_STR, 0)) || + sp->push_backpatch(i, ctx->push_label(thd, EMPTY_STR, 0)) || sp->add_cont_backpatch(i) || sp->add_instr(i)) MYSQL_YYABORT; - if (sp->restore_lex(YYTHD)) + if (sp->restore_lex(thd)) MYSQL_YYABORT; } sp_proc_stmts1 @@ -3974,7 +3966,7 @@ sp_if: sp->add_instr(i)) MYSQL_YYABORT; sp->backpatch(ctx->pop_label()); - sp->push_backpatch(i, ctx->push_label(YYTHD, EMPTY_STR, 0)); + sp->push_backpatch(i, ctx->push_label(thd, EMPTY_STR, 0)); } sp_elseifs { @@ -4000,7 +3992,7 @@ simple_case_stmt: { LEX *lex= Lex; case_stmt_action_case(lex); - lex->sphead->reset_lex(YYTHD); /* For expr $3 */ + lex->sphead->reset_lex(thd); /* For expr $3 */ } expr { @@ -4009,7 +4001,7 @@ simple_case_stmt: MYSQL_YYABORT; /* For expr $3 */ - if (lex->sphead->restore_lex(YYTHD)) + if (lex->sphead->restore_lex(thd)) MYSQL_YYABORT; } simple_when_clause_list @@ -4051,7 +4043,7 @@ searched_when_clause_list: simple_when_clause: WHEN_SYM { - Lex->sphead->reset_lex(YYTHD); /* For expr $3 */ + Lex->sphead->reset_lex(thd); /* For expr $3 */ } expr { @@ -4061,7 +4053,7 @@ simple_when_clause: if (case_stmt_action_when(lex, $3, true)) MYSQL_YYABORT; /* For expr $3 */ - if (lex->sphead->restore_lex(YYTHD)) + if (lex->sphead->restore_lex(thd)) MYSQL_YYABORT; } THEN_SYM @@ -4076,7 +4068,7 @@ simple_when_clause: searched_when_clause: WHEN_SYM { - Lex->sphead->reset_lex(YYTHD); /* For expr $3 */ + Lex->sphead->reset_lex(thd); /* For expr $3 */ } expr { @@ -4084,7 +4076,7 @@ searched_when_clause: if (case_stmt_action_when(lex, $3, false)) MYSQL_YYABORT; /* For expr $3 */ - if (lex->sphead->restore_lex(YYTHD)) + if (lex->sphead->restore_lex(thd)) MYSQL_YYABORT; } THEN_SYM @@ -4125,7 +4117,7 @@ sp_labeled_control: } else { - lab= lex->spcont->push_label(YYTHD, $1, lex->sphead->instructions()); + lab= lex->spcont->push_label(thd, $1, lex->sphead->instructions()); lab->type= sp_label::ITERATION; } } @@ -4164,7 +4156,7 @@ sp_labeled_block: MYSQL_YYABORT; } - lab= lex->spcont->push_label(YYTHD, $1, lex->sphead->instructions()); + lab= lex->spcont->push_label(thd, $1, lex->sphead->instructions()); lab->type= sp_label::BEGIN; } sp_block_content sp_opt_label @@ -4187,7 +4179,7 @@ sp_unlabeled_block: { /* Unlabeled blocks get a secret label. */ LEX *lex= Lex; uint ip= lex->sphead->instructions(); - sp_label *lab= lex->spcont->push_label(YYTHD, EMPTY_STR, ip); + sp_label *lab= lex->spcont->push_label(thd, EMPTY_STR, ip); lab->type= sp_label::BEGIN; } sp_block_content @@ -4203,7 +4195,7 @@ sp_block_content: 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(YYTHD, + lex->spcont= lex->spcont->push_context(thd, sp_pcontext::REGULAR_SCOPE); } sp_decls @@ -4247,7 +4239,7 @@ sp_unlabeled_control: MYSQL_YYABORT; } | WHILE_SYM - { Lex->sphead->reset_lex(YYTHD); } + { Lex->sphead->reset_lex(thd); } expr DO_SYM { LEX *lex= Lex; @@ -4261,7 +4253,7 @@ sp_unlabeled_control: sp->new_cont_backpatch(i) || sp->add_instr(i)) MYSQL_YYABORT; - if (sp->restore_lex(YYTHD)) + if (sp->restore_lex(thd)) MYSQL_YYABORT; } sp_proc_stmts1 END WHILE_SYM @@ -4276,7 +4268,7 @@ sp_unlabeled_control: lex->sphead->do_cont_backpatch(); } | REPEAT_SYM sp_proc_stmts1 UNTIL_SYM - { Lex->sphead->reset_lex(YYTHD); } + { Lex->sphead->reset_lex(thd); } expr END REPEAT_SYM { LEX *lex= Lex; @@ -4288,7 +4280,7 @@ sp_unlabeled_control: if (i == NULL || lex->sphead->add_instr(i)) MYSQL_YYABORT; - if (lex->sphead->restore_lex(YYTHD)) + if (lex->sphead->restore_lex(thd)) MYSQL_YYABORT; /* We can shortcut the cont_backpatch here */ i->m_cont_dest= ip+1; @@ -4653,8 +4645,8 @@ ts_wait: ; size_number: - real_ulong_num { $$= $1;} - | IDENT + real_ulonglong_num { $$= $1;} + | IDENT_sys { ulonglong number; uint text_shift_number= 0; @@ -4719,7 +4711,7 @@ create_body: { Lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; - TABLE_LIST *src_table= Lex->select_lex.add_table_to_list(YYTHD, + 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; @@ -5290,7 +5282,7 @@ part_value_expr_item: my_parse_error(ER(ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR)); MYSQL_YYABORT; } - if (part_info->add_column_list_value(YYTHD, part_expr)) + if (part_info->add_column_list_value(thd, part_expr)) { MYSQL_YYABORT; } @@ -5734,25 +5726,25 @@ create_table_option: } | IDENT_sys equal TEXT_STRING_sys { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, true, &Lex->create_info.option_list, &Lex->option_list_last); } | IDENT_sys equal ident { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, false, &Lex->create_info.option_list, &Lex->option_list_last); } | IDENT_sys equal real_ulonglong_num { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, &Lex->create_info.option_list, - &Lex->option_list_last, YYTHD->mem_root); + &Lex->option_list_last, thd->mem_root); } | IDENT_sys equal DEFAULT { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, &Lex->create_info.option_list, &Lex->option_list_last); } @@ -5796,19 +5788,19 @@ default_collation: storage_engines: ident_or_text { - plugin_ref plugin= ha_resolve_by_name(YYTHD, &$1); + plugin_ref plugin= ha_resolve_by_name(thd, &$1); if (plugin) $$= plugin_hton(plugin); else { - if (YYTHD->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION) + if (thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION) { my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), $1.str); MYSQL_YYABORT; } $$= 0; - push_warning_printf(YYTHD, Sql_condition::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_STORAGE_ENGINE, ER(ER_UNKNOWN_STORAGE_ENGINE), $1.str); @@ -5820,7 +5812,7 @@ known_storage_engines: ident_or_text { plugin_ref plugin; - if ((plugin= ha_resolve_by_name(YYTHD, &$1))) + if ((plugin= ha_resolve_by_name(thd, &$1))) $$= plugin_hton(plugin); else { @@ -5908,7 +5900,7 @@ key_def: if (add_create_index (Lex, $1, $3)) MYSQL_YYABORT; } - | opt_constraint constraint_key_type opt_ident key_alg + | opt_constraint constraint_key_type opt_if_not_exists_ident key_alg '(' key_list ')' { Lex->option_list= NULL; } normal_key_options @@ -6154,7 +6146,7 @@ type: { char buff[sizeof("YEAR()") + MY_INT64_NUM_DECIMAL_DIGITS + 1]; my_snprintf(buff, sizeof(buff), "YEAR(%lu)", length); - push_warning_printf(YYTHD, Sql_condition::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_WARN_DEPRECATED_SYNTAX, ER(ER_WARN_DEPRECATED_SYNTAX), buff, "YEAR(4)"); @@ -6168,7 +6160,7 @@ type: { $$=MYSQL_TYPE_TIME; } | TIMESTAMP opt_field_length { - if (YYTHD->variables.sql_mode & MODE_MAXDB) + if (thd->variables.sql_mode & MODE_MAXDB) $$=MYSQL_TYPE_DATETIME; else { @@ -6300,7 +6292,7 @@ int_type: real_type: REAL { - $$= YYTHD->variables.sql_mode & MODE_REAL_AS_FLOAT ? + $$= thd->variables.sql_mode & MODE_REAL_AS_FLOAT ? MYSQL_TYPE_FLOAT : MYSQL_TYPE_DOUBLE; } | DOUBLE_SYM @@ -6375,7 +6367,7 @@ attribute: | DEFAULT now_or_signed_literal { Lex->default_value=$2; } | ON UPDATE_SYM NOW_SYM opt_default_time_precision { - Item *item= new (YYTHD->mem_root) Item_func_now_local($4); + Item *item= new (thd->mem_root) Item_func_now_local($4); if (item == NULL) MYSQL_YYABORT; Lex->on_update_value= item; @@ -6421,25 +6413,25 @@ attribute: } | IDENT_sys equal TEXT_STRING_sys { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, true, &Lex->option_list, &Lex->option_list_last); } | IDENT_sys equal ident { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, false, &Lex->option_list, &Lex->option_list_last); } | IDENT_sys equal real_ulonglong_num { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, &Lex->option_list, - &Lex->option_list_last, YYTHD->mem_root); + &Lex->option_list_last, thd->mem_root); } | IDENT_sys equal DEFAULT { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, &Lex->option_list, &Lex->option_list_last); } ; @@ -6469,7 +6461,7 @@ type_with_opt_collate: now_or_signed_literal: NOW_SYM opt_default_time_precision { - $$= new (YYTHD->mem_root) Item_func_now_local($2); + $$= new (thd->mem_root) Item_func_now_local($2); if ($$ == NULL) MYSQL_YYABORT; } @@ -6530,11 +6522,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; - } } ; @@ -6578,19 +6567,13 @@ unicode: } | UNICODE_SYM BINARY { - if (!(Lex->charset=get_charset_by_name("ucs2_bin", MYF(0)))) - { - my_error(ER_UNKNOWN_COLLATION, MYF(0), "ucs2_bin"); + if (!(Lex->charset= mysqld_collation_get_by_name("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"); + if (!(Lex->charset= mysqld_collation_get_by_name("ucs2_bin"))) MYSQL_YYABORT; - } } ; @@ -6617,6 +6600,74 @@ opt_bin_mod: | BINARY { Lex->type|= BINCMP_FLAG; } ; +ws_nweights: + '(' real_ulong_num + { + if ($2 == 0) + { + my_parse_error(ER(ER_SYNTAX_ERROR)); + MYSQL_YYABORT; + } + } + ')' + { $$= $2; } + ; + +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 */ | PRIMARY_SYM @@ -6817,25 +6868,25 @@ all_key_opt: | COMMENT_SYM TEXT_STRING_sys { Lex->key_create_info.comment= $2; } | IDENT_sys equal TEXT_STRING_sys { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, true, &Lex->option_list, &Lex->option_list_last); } | IDENT_sys equal ident { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, false, &Lex->option_list, &Lex->option_list_last); } | IDENT_sys equal real_ulonglong_num { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, $3, &Lex->option_list, - &Lex->option_list_last, YYTHD->mem_root); + &Lex->option_list_last, thd->mem_root); } | IDENT_sys equal DEFAULT { - new (YYTHD->mem_root) + new (thd->mem_root) engine_option_value($1, &Lex->option_list, &Lex->option_list_last); } ; @@ -6944,7 +6995,7 @@ alter: } alter_options TABLE_SYM table_ident { - if (!Lex->select_lex.add_table_to_list(YYTHD, $5, NULL, + if (!Lex->select_lex.add_table_to_list(thd, $5, NULL, TL_OPTION_UPDATING, TL_READ_NO_INSERT, MDL_SHARED_UPGRADABLE)) @@ -6957,7 +7008,7 @@ alter: if (!Lex->m_sql_cmd) { /* Create a generic ALTER TABLE statment. */ - Lex->m_sql_cmd= new (YYTHD->mem_root) Sql_cmd_alter_table(); + Lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_alter_table(); if (Lex->m_sql_cmd == NULL) MYSQL_YYABORT; } @@ -7065,7 +7116,7 @@ alter: Event_parse_data. */ - if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) + if (!(Lex->event_parse_data= Event_parse_data::new_instance(thd))) MYSQL_YYABORT; Lex->event_parse_data->identifier= $4; @@ -7151,7 +7202,7 @@ alter_commands: /* empty */ | DISCARD TABLESPACE { - Lex->m_sql_cmd= new (YYTHD->mem_root) + 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) @@ -7159,7 +7210,7 @@ alter_commands: } | IMPORT TABLESPACE { - Lex->m_sql_cmd= new (YYTHD->mem_root) + 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) @@ -7193,7 +7244,6 @@ alter_commands: | OPTIMIZE PARTITION_SYM opt_no_write_to_binlog all_or_alt_part_name_list { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); @@ -7207,7 +7257,6 @@ alter_commands: | ANALYZE_SYM PARTITION_SYM opt_no_write_to_binlog all_or_alt_part_name_list { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); @@ -7219,7 +7268,6 @@ alter_commands: } | CHECK_SYM PARTITION_SYM all_or_alt_part_name_list { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->check_opt.init(); DBUG_ASSERT(!lex->m_sql_cmd); @@ -7232,7 +7280,6 @@ alter_commands: | REPAIR PARTITION_SYM opt_no_write_to_binlog all_or_alt_part_name_list { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); @@ -7252,7 +7299,6 @@ alter_commands: } | TRUNCATE_SYM PARTITION_SYM all_or_alt_part_name_list { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->check_opt.init(); DBUG_ASSERT(!lex->m_sql_cmd); @@ -7265,7 +7311,6 @@ alter_commands: | EXCHANGE_SYM PARTITION_SYM alt_part_name_item WITH TABLE_SYM table_ident have_partitioning { - THD *thd= YYTHD; LEX *lex= thd->lex; size_t dummy; lex->select_lex.db=$6->db.str; @@ -7543,7 +7588,6 @@ alter_list_item: { if (!$4) { - THD *thd= YYTHD; $4= thd->variables.collation_database; } $5= $5 ? $5 : $4; @@ -7650,8 +7694,8 @@ alter_option: IGNORE_SYM { Lex->ignore= 1;} | ONLINE_SYM { - Lex->alter_info.requested_algorithm= - Alter_info::ALTER_TABLE_ALGORITHM_INPLACE; + Lex->alter_info.requested_lock= + Alter_info::ALTER_TABLE_LOCK_NONE; } @@ -7842,7 +7886,6 @@ repair: } table_list opt_mi_repair_type { - THD *thd= YYTHD; LEX* lex= thd->lex; DBUG_ASSERT(!lex->m_sql_cmd); lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_repair_table(); @@ -7880,7 +7923,6 @@ analyze: } analyze_table_list { - THD *thd= YYTHD; LEX* lex= thd->lex; DBUG_ASSERT(!lex->m_sql_cmd); lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_analyze_table(); @@ -7903,7 +7945,6 @@ opt_persistent_stat_clause: {} | PERSISTENT_SYM FOR_SYM persistent_stat_spec { - THD *thd= YYTHD; thd->lex->with_persistent_for_clause= TRUE; } ; @@ -7918,7 +7959,6 @@ persistent_column_stat_spec: ALL {} | '(' { - THD *thd= YYTHD; LEX* lex= thd->lex; lex->column_list= new List<LEX_STRING>; if (lex->column_list == NULL) @@ -7932,7 +7972,6 @@ persistent_index_stat_spec: ALL {} | '(' { - THD *thd= YYTHD; LEX* lex= thd->lex; lex->index_list= new List<LEX_STRING>; if (lex->index_list == NULL) @@ -8005,7 +8044,6 @@ check: } table_list opt_mi_check_type { - THD *thd= YYTHD; LEX* lex= thd->lex; DBUG_ASSERT(!lex->m_sql_cmd); lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_check_table(); @@ -8046,7 +8084,6 @@ optimize: } table_list { - THD *thd= YYTHD; LEX* lex= thd->lex; DBUG_ASSERT(!lex->m_sql_cmd); lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_optimize_table(); @@ -8131,7 +8168,7 @@ keycache_list: assign_to_keycache: table_ident cache_keys_spec { - if (!Select->add_table_to_list(YYTHD, $1, NULL, 0, TL_READ, + if (!Select->add_table_to_list(thd, $1, NULL, 0, TL_READ, MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; @@ -8141,7 +8178,7 @@ assign_to_keycache: assign_to_keycache_parts: table_ident adm_partition cache_keys_spec { - if (!Select->add_table_to_list(YYTHD, $1, NULL, 0, TL_READ, + if (!Select->add_table_to_list(thd, $1, NULL, 0, TL_READ, MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; @@ -8177,7 +8214,7 @@ preload_list: preload_keys: table_ident cache_keys_spec opt_ignore_leaves { - if (!Select->add_table_to_list(YYTHD, $1, NULL, $3, TL_READ, + if (!Select->add_table_to_list(thd, $1, NULL, $3, TL_READ, MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; @@ -8187,7 +8224,7 @@ preload_keys: preload_keys_parts: table_ident adm_partition cache_keys_spec opt_ignore_leaves { - if (!Select->add_table_to_list(YYTHD, $1, NULL, $4, TL_READ, + if (!Select->add_table_to_list(thd, $1, NULL, $4, TL_READ, MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; @@ -8204,7 +8241,7 @@ adm_partition: cache_keys_spec: { - Lex->select_lex.alloc_index_hints(YYTHD); + Lex->select_lex.alloc_index_hints(thd); Select->set_index_hint_type(INDEX_HINT_USE, INDEX_HINT_MASK_ALL); } @@ -8417,7 +8454,6 @@ select_item_list: | select_item | '*' { - THD *thd= YYTHD; Item *item= new (thd->mem_root) Item_field(&thd->lex->current_select->context, NULL, NULL, "*"); @@ -8432,14 +8468,11 @@ select_item_list: select_item: remember_name table_wild remember_end { - THD *thd= YYTHD; - if (add_item_to_list(thd, $2)) MYSQL_YYABORT; } | remember_name expr remember_end select_alias { - THD *thd= YYTHD; DBUG_ASSERT($1 < $3); if (add_item_to_list(thd, $2)) @@ -8547,7 +8580,7 @@ expr: else { /* X OR Y */ - $$ = new (YYTHD->mem_root) Item_cond_or($1, $3); + $$ = new (thd->mem_root) Item_cond_or($1, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -8555,7 +8588,7 @@ expr: | expr XOR expr %prec XOR { /* XOR is a proprietary extension */ - $$ = new (YYTHD->mem_root) Item_func_xor($1, $3); + $$ = new (thd->mem_root) Item_func_xor($1, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -8597,50 +8630,50 @@ expr: else { /* X AND Y */ - $$ = new (YYTHD->mem_root) Item_cond_and($1, $3); + $$ = new (thd->mem_root) Item_cond_and($1, $3); if ($$ == NULL) MYSQL_YYABORT; } } | NOT_SYM expr %prec NOT_SYM { - $$= negate_expression(YYTHD, $2); + $$= negate_expression(thd, $2); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS TRUE_SYM %prec IS { - $$= new (YYTHD->mem_root) Item_func_istrue($1); + $$= new (thd->mem_root) Item_func_istrue($1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS not TRUE_SYM %prec IS { - $$= new (YYTHD->mem_root) Item_func_isnottrue($1); + $$= new (thd->mem_root) Item_func_isnottrue($1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS FALSE_SYM %prec IS { - $$= new (YYTHD->mem_root) Item_func_isfalse($1); + $$= new (thd->mem_root) Item_func_isfalse($1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS not FALSE_SYM %prec IS { - $$= new (YYTHD->mem_root) Item_func_isnotfalse($1); + $$= new (thd->mem_root) Item_func_isnotfalse($1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS UNKNOWN_SYM %prec IS { - $$= new (YYTHD->mem_root) Item_func_isnull($1); + $$= new (thd->mem_root) Item_func_isnull($1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS not UNKNOWN_SYM %prec IS { - $$= new (YYTHD->mem_root) Item_func_isnotnull($1); + $$= new (thd->mem_root) Item_func_isnotnull($1); if ($$ == NULL) MYSQL_YYABORT; } @@ -8650,19 +8683,19 @@ expr: bool_pri: bool_pri IS NULL_SYM %prec IS { - $$= new (YYTHD->mem_root) Item_func_isnull($1); + $$= new (thd->mem_root) Item_func_isnull($1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri IS not NULL_SYM %prec IS { - $$= new (YYTHD->mem_root) Item_func_isnotnull($1); + $$= new (thd->mem_root) Item_func_isnotnull($1); if ($$ == NULL) MYSQL_YYABORT; } | bool_pri EQUAL_SYM predicate %prec EQUAL_SYM { - $$= new (YYTHD->mem_root) Item_func_equal($1,$3); + $$= new (thd->mem_root) Item_func_equal($1,$3); if ($$ == NULL) MYSQL_YYABORT; } @@ -8684,13 +8717,12 @@ bool_pri: predicate: bit_expr IN_SYM '(' subselect ')' { - $$= new (YYTHD->mem_root) Item_in_subselect($1, $4); + $$= new (thd->mem_root) Item_in_subselect($1, $4); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not IN_SYM '(' subselect ')' { - THD *thd= YYTHD; Item *item= new (thd->mem_root) Item_in_subselect($1, $5); if (item == NULL) MYSQL_YYABORT; @@ -8700,7 +8732,7 @@ predicate: } | bit_expr IN_SYM '(' expr ')' { - $$= handle_sql2003_note184_exception(YYTHD, $1, true, $4); + $$= handle_sql2003_note184_exception(thd, $1, true, $4); if ($$ == NULL) MYSQL_YYABORT; } @@ -8708,13 +8740,13 @@ predicate: { $6->push_front($4); $6->push_front($1); - $$= new (YYTHD->mem_root) Item_func_in(*$6); + $$= new (thd->mem_root) Item_func_in(*$6); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not IN_SYM '(' expr ')' { - $$= handle_sql2003_note184_exception(YYTHD, $1, false, $5); + $$= handle_sql2003_note184_exception(thd, $1, false, $5); if ($$ == NULL) MYSQL_YYABORT; } @@ -8722,7 +8754,7 @@ predicate: { $7->push_front($5); $7->push_front($1); - Item_func_in *item = new (YYTHD->mem_root) Item_func_in(*$7); + Item_func_in *item = new (thd->mem_root) Item_func_in(*$7); if (item == NULL) MYSQL_YYABORT; item->negate(); @@ -8730,14 +8762,14 @@ predicate: } | bit_expr BETWEEN_SYM bit_expr AND_SYM predicate { - $$= new (YYTHD->mem_root) Item_func_between($1,$3,$5); + $$= new (thd->mem_root) Item_func_between($1,$3,$5); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not BETWEEN_SYM bit_expr AND_SYM predicate { Item_func_between *item; - item= new (YYTHD->mem_root) Item_func_between($1,$4,$6); + item= new (thd->mem_root) Item_func_between($1,$4,$6); if (item == NULL) MYSQL_YYABORT; item->negate(); @@ -8745,42 +8777,42 @@ predicate: } | bit_expr SOUNDS_SYM LIKE bit_expr { - Item *item1= new (YYTHD->mem_root) Item_func_soundex($1); - Item *item4= new (YYTHD->mem_root) Item_func_soundex($4); + Item *item1= new (thd->mem_root) Item_func_soundex($1); + Item *item4= new (thd->mem_root) Item_func_soundex($4); if ((item1 == NULL) || (item4 == NULL)) MYSQL_YYABORT; - $$= new (YYTHD->mem_root) Item_func_eq(item1, item4); + $$= new (thd->mem_root) Item_func_eq(item1, item4); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr LIKE simple_expr opt_escape { - $$= new (YYTHD->mem_root) Item_func_like($1,$3,$4,Lex->escape_used); + $$= new (thd->mem_root) Item_func_like($1,$3,$4,Lex->escape_used); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not LIKE simple_expr opt_escape { - Item *item= new (YYTHD->mem_root) Item_func_like($1,$4,$5, + Item *item= new (thd->mem_root) Item_func_like($1,$4,$5, Lex->escape_used); if (item == NULL) MYSQL_YYABORT; - $$= new (YYTHD->mem_root) Item_func_not(item); + $$= new (thd->mem_root) Item_func_not(item); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr REGEXP bit_expr { - $$= new (YYTHD->mem_root) Item_func_regex($1,$3); + $$= new (thd->mem_root) Item_func_regex($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr not REGEXP bit_expr { - Item *item= new (YYTHD->mem_root) Item_func_regex($1,$4); + Item *item= new (thd->mem_root) Item_func_regex($1,$4); if (item == NULL) MYSQL_YYABORT; - $$= negate_expression(YYTHD, item); + $$= negate_expression(thd, item); if ($$ == NULL) MYSQL_YYABORT; } @@ -8790,85 +8822,85 @@ predicate: bit_expr: bit_expr '|' bit_expr %prec '|' { - $$= new (YYTHD->mem_root) Item_func_bit_or($1,$3); + $$= new (thd->mem_root) Item_func_bit_or($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '&' bit_expr %prec '&' { - $$= new (YYTHD->mem_root) Item_func_bit_and($1,$3); + $$= new (thd->mem_root) Item_func_bit_and($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr SHIFT_LEFT bit_expr %prec SHIFT_LEFT { - $$= new (YYTHD->mem_root) Item_func_shift_left($1,$3); + $$= new (thd->mem_root) Item_func_shift_left($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr SHIFT_RIGHT bit_expr %prec SHIFT_RIGHT { - $$= new (YYTHD->mem_root) Item_func_shift_right($1,$3); + $$= new (thd->mem_root) Item_func_shift_right($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '+' bit_expr %prec '+' { - $$= new (YYTHD->mem_root) Item_func_plus($1,$3); + $$= new (thd->mem_root) Item_func_plus($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '-' bit_expr %prec '-' { - $$= new (YYTHD->mem_root) Item_func_minus($1,$3); + $$= new (thd->mem_root) Item_func_minus($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '+' INTERVAL_SYM expr interval %prec '+' { - $$= new (YYTHD->mem_root) Item_date_add_interval($1,$4,$5,0); + $$= new (thd->mem_root) Item_date_add_interval($1,$4,$5,0); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '-' INTERVAL_SYM expr interval %prec '-' { - $$= new (YYTHD->mem_root) Item_date_add_interval($1,$4,$5,1); + $$= new (thd->mem_root) Item_date_add_interval($1,$4,$5,1); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '*' bit_expr %prec '*' { - $$= new (YYTHD->mem_root) Item_func_mul($1,$3); + $$= new (thd->mem_root) Item_func_mul($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '/' bit_expr %prec '/' { - $$= new (YYTHD->mem_root) Item_func_div($1,$3); + $$= new (thd->mem_root) Item_func_div($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '%' bit_expr %prec '%' { - $$= new (YYTHD->mem_root) Item_func_mod($1,$3); + $$= new (thd->mem_root) Item_func_mod($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr DIV_SYM bit_expr %prec DIV_SYM { - $$= new (YYTHD->mem_root) Item_func_int_div($1,$3); + $$= new (thd->mem_root) Item_func_int_div($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr MOD_SYM bit_expr %prec MOD_SYM { - $$= new (YYTHD->mem_root) Item_func_mod($1,$3); + $$= new (thd->mem_root) Item_func_mod($1,$3); if ($$ == NULL) MYSQL_YYABORT; } | bit_expr '^' bit_expr { - $$= new (YYTHD->mem_root) Item_func_bit_xor($1,$3); + $$= new (thd->mem_root) Item_func_bit_xor($1,$3); if ($$ == NULL) MYSQL_YYABORT; } @@ -9004,7 +9036,7 @@ dyncall_create_element: { LEX *lex= Lex; $$= (DYNCALL_CREATE_DEF *) - alloc_root(YYTHD->mem_root, sizeof(DYNCALL_CREATE_DEF)); + alloc_root(thd->mem_root, sizeof(DYNCALL_CREATE_DEF)); if ($$ == NULL) MYSQL_YYABORT; $$->key= $1; @@ -9024,7 +9056,7 @@ dyncall_create_element: dyncall_create_list: dyncall_create_element { - $$= new (YYTHD->mem_root) List<DYNCALL_CREATE_DEF>; + $$= new (thd->mem_root) List<DYNCALL_CREATE_DEF>; if ($$ == NULL) MYSQL_YYABORT; $$->push_back($1); @@ -9044,7 +9076,6 @@ simple_expr: | function_call_conflict | simple_expr COLLATE_SYM ident_or_text %prec NEG { - THD *thd= YYTHD; Item *i1= new (thd->mem_root) Item_string($3.str, $3.length, thd->charset()); @@ -9060,7 +9091,7 @@ simple_expr: | sum_expr | simple_expr OR_OR_SYM simple_expr { - $$= new (YYTHD->mem_root) Item_func_concat($1, $3); + $$= new (thd->mem_root) Item_func_concat($1, $3); if ($$ == NULL) MYSQL_YYABORT; } @@ -9070,25 +9101,25 @@ simple_expr: } | '-' simple_expr %prec NEG { - $$= new (YYTHD->mem_root) Item_func_neg($2); + $$= new (thd->mem_root) Item_func_neg($2); if ($$ == NULL) MYSQL_YYABORT; } | '~' simple_expr %prec NEG { - $$= new (YYTHD->mem_root) Item_func_bit_neg($2); + $$= new (thd->mem_root) Item_func_bit_neg($2); if ($$ == NULL) MYSQL_YYABORT; } | not2 simple_expr %prec NEG { - $$= negate_expression(YYTHD, $2); + $$= negate_expression(thd, $2); if ($$ == NULL) MYSQL_YYABORT; } | '(' subselect ')' { - $$= new (YYTHD->mem_root) Item_singlerow_subselect($2); + $$= new (thd->mem_root) Item_singlerow_subselect($2); if ($$ == NULL) MYSQL_YYABORT; } @@ -9097,20 +9128,20 @@ simple_expr: | '(' expr ',' expr_list ')' { $4->push_front($2); - $$= new (YYTHD->mem_root) Item_row(*$4); + $$= new (thd->mem_root) Item_row(*$4); if ($$ == NULL) MYSQL_YYABORT; } | ROW_SYM '(' expr ',' expr_list ')' { $5->push_front($3); - $$= new (YYTHD->mem_root) Item_row(*$5); + $$= new (thd->mem_root) Item_row(*$5); if ($$ == NULL) MYSQL_YYABORT; } | EXISTS '(' subselect ')' { - $$= new (YYTHD->mem_root) Item_exists_subselect($3); + $$= new (thd->mem_root) Item_exists_subselect($3); if ($$ == NULL) MYSQL_YYABORT; } @@ -9147,7 +9178,7 @@ simple_expr: } if (type != MYSQL_TYPE_STRING) { - $$= create_temporal_literal(YYTHD, + $$= create_temporal_literal(thd, item->str_value.ptr(), item->str_value.length(), item->str_value.charset(), @@ -9160,7 +9191,7 @@ simple_expr: | MATCH ident_list_arg AGAINST '(' bit_expr fulltext_options ')' { $2->push_front($5); - Item_func_match *i1= new (YYTHD->mem_root) Item_func_match(*$2, $6); + Item_func_match *i1= new (thd->mem_root) Item_func_match(*$2, $6); if (i1 == NULL) MYSQL_YYABORT; Select->add_ftfunc_to_list(i1); @@ -9168,7 +9199,7 @@ simple_expr: } | BINARY simple_expr %prec NEG { - $$= create_func_cast(YYTHD, $2, ITEM_CAST_CHAR, NULL, NULL, + $$= create_func_cast(thd, $2, ITEM_CAST_CHAR, NULL, NULL, &my_charset_bin); if ($$ == NULL) MYSQL_YYABORT; @@ -9176,27 +9207,27 @@ simple_expr: | CAST_SYM '(' expr AS cast_type ')' { LEX *lex= Lex; - $$= create_func_cast(YYTHD, $3, $5, lex->length, lex->dec, + $$= create_func_cast(thd, $3, $5, lex->length, lex->dec, lex->charset); if ($$ == NULL) MYSQL_YYABORT; } | CASE_SYM opt_expr when_list opt_else END { - $$= new (YYTHD->mem_root) Item_func_case(* $3, $2, $4 ); + $$= new (thd->mem_root) Item_func_case(* $3, $2, $4 ); if ($$ == NULL) MYSQL_YYABORT; } | CONVERT_SYM '(' expr ',' cast_type ')' { - $$= create_func_cast(YYTHD, $3, $5, Lex->length, Lex->dec, + $$= create_func_cast(thd, $3, $5, Lex->length, Lex->dec, Lex->charset); if ($$ == NULL) MYSQL_YYABORT; } | CONVERT_SYM '(' expr USING charset_name ')' { - $$= new (YYTHD->mem_root) Item_func_conv_charset($3,$5); + $$= new (thd->mem_root) Item_func_conv_charset($3,$5); if ($$ == NULL) MYSQL_YYABORT; } @@ -9209,14 +9240,14 @@ simple_expr: my_error(ER_WRONG_COLUMN_NAME, MYF(0), il->my_name()->str); MYSQL_YYABORT; } - $$= new (YYTHD->mem_root) Item_default_value(Lex->current_context(), + $$= new (thd->mem_root) Item_default_value(Lex->current_context(), $3); if ($$ == NULL) MYSQL_YYABORT; } | VALUES '(' simple_ident_nospvar ')' { - $$= new (YYTHD->mem_root) Item_insert_value(Lex->current_context(), + $$= new (thd->mem_root) Item_insert_value(Lex->current_context(), $3); if ($$ == NULL) MYSQL_YYABORT; @@ -9224,7 +9255,7 @@ simple_expr: | INTERVAL_SYM expr interval '+' expr %prec INTERVAL_SYM /* we cannot put interval before - */ { - $$= new (YYTHD->mem_root) Item_date_add_interval($5,$2,$3,0); + $$= new (thd->mem_root) Item_date_add_interval($5,$2,$3,0); if ($$ == NULL) MYSQL_YYABORT; } @@ -9239,19 +9270,27 @@ simple_expr: function_call_keyword: CHAR_SYM '(' expr_list ')' { - $$= new (YYTHD->mem_root) Item_func_char(*$3); + $$= new (thd->mem_root) Item_func_char(*$3); if ($$ == NULL) MYSQL_YYABORT; } | CHAR_SYM '(' expr_list USING charset_name ')' { - $$= new (YYTHD->mem_root) Item_func_char(*$3, $5); + $$= new (thd->mem_root) Item_func_char(*$3, $5); if ($$ == NULL) MYSQL_YYABORT; } | CURRENT_USER optional_braces { - $$= new (YYTHD->mem_root) Item_func_current_user(Lex->current_context()); + $$= new (thd->mem_root) Item_func_current_user(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(Lex->current_context()); if ($$ == NULL) MYSQL_YYABORT; Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); @@ -9259,31 +9298,30 @@ function_call_keyword: } | DATE_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_date_typecast($3); + $$= new (thd->mem_root) Item_date_typecast($3); if ($$ == NULL) MYSQL_YYABORT; } | DAY_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_dayofmonth($3); + $$= new (thd->mem_root) Item_func_dayofmonth($3); if ($$ == NULL) MYSQL_YYABORT; } | HOUR_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_hour($3); + $$= new (thd->mem_root) Item_func_hour($3); if ($$ == NULL) MYSQL_YYABORT; } | INSERT '(' expr ',' expr ',' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_insert($3,$5,$7,$9); + $$= new (thd->mem_root) Item_func_insert($3,$5,$7,$9); if ($$ == NULL) MYSQL_YYABORT; } | INTERVAL_SYM '(' expr ',' expr ')' %prec INTERVAL_SYM { - THD *thd= YYTHD; List<Item> *list= new (thd->mem_root) List<Item>; if (list == NULL) MYSQL_YYABORT; @@ -9298,7 +9336,6 @@ function_call_keyword: } | INTERVAL_SYM '(' expr ',' expr ',' expr_list ')' %prec INTERVAL_SYM { - THD *thd= YYTHD; $7->push_front($5); $7->push_front($3); Item_row *item= new (thd->mem_root) Item_row(*$7); @@ -9310,103 +9347,103 @@ function_call_keyword: } | LEFT '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_left($3,$5); + $$= new (thd->mem_root) Item_func_left($3,$5); if ($$ == NULL) MYSQL_YYABORT; } | MINUTE_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_minute($3); + $$= new (thd->mem_root) Item_func_minute($3); if ($$ == NULL) MYSQL_YYABORT; } | MONTH_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_month($3); + $$= new (thd->mem_root) Item_func_month($3); if ($$ == NULL) MYSQL_YYABORT; } | RIGHT '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_right($3,$5); + $$= new (thd->mem_root) Item_func_right($3,$5); if ($$ == NULL) MYSQL_YYABORT; } | SECOND_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_second($3); + $$= new (thd->mem_root) Item_func_second($3); if ($$ == NULL) MYSQL_YYABORT; } | TIME_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_time_typecast($3, AUTO_SEC_PART_DIGITS); + $$= new (thd->mem_root) Item_time_typecast($3, AUTO_SEC_PART_DIGITS); if ($$ == NULL) MYSQL_YYABORT; } | TIMESTAMP '(' expr ')' { - $$= new (YYTHD->mem_root) Item_datetime_typecast($3, AUTO_SEC_PART_DIGITS); + $$= new (thd->mem_root) Item_datetime_typecast($3, AUTO_SEC_PART_DIGITS); if ($$ == NULL) MYSQL_YYABORT; } | TIMESTAMP '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_add_time($3, $5, 1, 0); + $$= new (thd->mem_root) Item_func_add_time($3, $5, 1, 0); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_trim($3); + $$= new (thd->mem_root) Item_func_trim($3); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' LEADING expr FROM expr ')' { - $$= new (YYTHD->mem_root) Item_func_ltrim($6,$4); + $$= new (thd->mem_root) Item_func_ltrim($6,$4); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' TRAILING expr FROM expr ')' { - $$= new (YYTHD->mem_root) Item_func_rtrim($6,$4); + $$= new (thd->mem_root) Item_func_rtrim($6,$4); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' BOTH expr FROM expr ')' { - $$= new (YYTHD->mem_root) Item_func_trim($6,$4); + $$= new (thd->mem_root) Item_func_trim($6,$4); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' LEADING FROM expr ')' { - $$= new (YYTHD->mem_root) Item_func_ltrim($5); + $$= new (thd->mem_root) Item_func_ltrim($5); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' TRAILING FROM expr ')' { - $$= new (YYTHD->mem_root) Item_func_rtrim($5); + $$= new (thd->mem_root) Item_func_rtrim($5); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' BOTH FROM expr ')' { - $$= new (YYTHD->mem_root) Item_func_trim($5); + $$= new (thd->mem_root) Item_func_trim($5); if ($$ == NULL) MYSQL_YYABORT; } | TRIM '(' expr FROM expr ')' { - $$= new (YYTHD->mem_root) Item_func_trim($5,$3); + $$= new (thd->mem_root) Item_func_trim($5,$3); if ($$ == NULL) MYSQL_YYABORT; } | USER '(' ')' { - $$= new (YYTHD->mem_root) Item_func_user(); + $$= new (thd->mem_root) Item_func_user(); if ($$ == NULL) MYSQL_YYABORT; Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); @@ -9414,7 +9451,7 @@ function_call_keyword: } | YEAR_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_year($3); + $$= new (thd->mem_root) Item_func_year($3); if ($$ == NULL) MYSQL_YYABORT; } @@ -9435,27 +9472,27 @@ function_call_keyword: function_call_nonkeyword: ADDDATE_SYM '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_date_add_interval($3, $5, + $$= new (thd->mem_root) Item_date_add_interval($3, $5, INTERVAL_DAY, 0); if ($$ == NULL) MYSQL_YYABORT; } | ADDDATE_SYM '(' expr ',' INTERVAL_SYM expr interval ')' { - $$= new (YYTHD->mem_root) Item_date_add_interval($3, $6, $7, 0); + $$= new (thd->mem_root) Item_date_add_interval($3, $6, $7, 0); if ($$ == NULL) MYSQL_YYABORT; } | CURDATE optional_braces { - $$= new (YYTHD->mem_root) Item_func_curdate_local(); + $$= new (thd->mem_root) Item_func_curdate_local(); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | CURTIME opt_time_precision { - $$= new (YYTHD->mem_root) Item_func_curtime_local($2); + $$= new (thd->mem_root) Item_func_curtime_local($2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; @@ -9463,76 +9500,76 @@ function_call_nonkeyword: | DATE_ADD_INTERVAL '(' expr ',' INTERVAL_SYM expr interval ')' %prec INTERVAL_SYM { - $$= new (YYTHD->mem_root) Item_date_add_interval($3,$6,$7,0); + $$= new (thd->mem_root) Item_date_add_interval($3,$6,$7,0); if ($$ == NULL) MYSQL_YYABORT; } | DATE_SUB_INTERVAL '(' expr ',' INTERVAL_SYM expr interval ')' %prec INTERVAL_SYM { - $$= new (YYTHD->mem_root) Item_date_add_interval($3,$6,$7,1); + $$= new (thd->mem_root) Item_date_add_interval($3,$6,$7,1); if ($$ == NULL) MYSQL_YYABORT; } | EXTRACT_SYM '(' interval FROM expr ')' { - $$=new (YYTHD->mem_root) Item_extract( $3, $5); + $$=new (thd->mem_root) Item_extract( $3, $5); if ($$ == NULL) MYSQL_YYABORT; } | GET_FORMAT '(' date_time_type ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_get_format($3, $5); + $$= new (thd->mem_root) Item_func_get_format($3, $5); if ($$ == NULL) MYSQL_YYABORT; } | NOW_SYM opt_time_precision { - $$= new (YYTHD->mem_root) Item_func_now_local($2); + $$= new (thd->mem_root) Item_func_now_local($2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | POSITION_SYM '(' bit_expr IN_SYM expr ')' { - $$ = new (YYTHD->mem_root) Item_func_locate($5,$3); + $$ = new (thd->mem_root) Item_func_locate($5,$3); if ($$ == NULL) MYSQL_YYABORT; } | SUBDATE_SYM '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_date_add_interval($3, $5, + $$= new (thd->mem_root) Item_date_add_interval($3, $5, INTERVAL_DAY, 1); if ($$ == NULL) MYSQL_YYABORT; } | SUBDATE_SYM '(' expr ',' INTERVAL_SYM expr interval ')' { - $$= new (YYTHD->mem_root) Item_date_add_interval($3, $6, $7, 1); + $$= new (thd->mem_root) Item_date_add_interval($3, $6, $7, 1); if ($$ == NULL) MYSQL_YYABORT; } | SUBSTRING '(' expr ',' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_substr($3,$5,$7); + $$= new (thd->mem_root) Item_func_substr($3,$5,$7); if ($$ == NULL) MYSQL_YYABORT; } | SUBSTRING '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_substr($3,$5); + $$= new (thd->mem_root) Item_func_substr($3,$5); if ($$ == NULL) MYSQL_YYABORT; } | SUBSTRING '(' expr FROM expr FOR_SYM expr ')' { - $$= new (YYTHD->mem_root) Item_func_substr($3,$5,$7); + $$= new (thd->mem_root) Item_func_substr($3,$5,$7); if ($$ == NULL) MYSQL_YYABORT; } | SUBSTRING '(' expr FROM expr ')' { - $$= new (YYTHD->mem_root) Item_func_substr($3,$5); + $$= new (thd->mem_root) Item_func_substr($3,$5); if ($$ == NULL) MYSQL_YYABORT; } @@ -9547,42 +9584,42 @@ function_call_nonkeyword: */ Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); if (global_system_variables.sysdate_is_now == 0) - $$= new (YYTHD->mem_root) Item_func_sysdate_local($2); + $$= new (thd->mem_root) Item_func_sysdate_local($2); else - $$= new (YYTHD->mem_root) Item_func_now_local($2); + $$= new (thd->mem_root) Item_func_now_local($2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | TIMESTAMP_ADD '(' interval_time_stamp ',' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_date_add_interval($7,$5,$3,0); + $$= new (thd->mem_root) Item_date_add_interval($7,$5,$3,0); if ($$ == NULL) MYSQL_YYABORT; } | TIMESTAMP_DIFF '(' interval_time_stamp ',' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_timestamp_diff($5,$7,$3); + $$= new (thd->mem_root) Item_func_timestamp_diff($5,$7,$3); if ($$ == NULL) MYSQL_YYABORT; } | UTC_DATE_SYM optional_braces { - $$= new (YYTHD->mem_root) Item_func_curdate_utc(); + $$= new (thd->mem_root) Item_func_curdate_utc(); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | UTC_TIME_SYM opt_time_precision { - $$= new (YYTHD->mem_root) Item_func_curtime_utc($2); + $$= new (thd->mem_root) Item_func_curtime_utc($2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | UTC_TIMESTAMP_SYM opt_time_precision { - $$= new (YYTHD->mem_root) Item_func_now_utc($2); + $$= new (thd->mem_root) Item_func_now_utc($2); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; @@ -9590,28 +9627,28 @@ function_call_nonkeyword: | COLUMN_ADD_SYM '(' expr ',' dyncall_create_list ')' { - $$= create_func_dyncol_add(YYTHD, $3, *$5); + $$= create_func_dyncol_add(thd, $3, *$5); if ($$ == NULL) MYSQL_YYABORT; } | COLUMN_DELETE_SYM '(' expr ',' expr_list ')' { - $$= create_func_dyncol_delete(YYTHD, $3, *$5); + $$= create_func_dyncol_delete(thd, $3, *$5); if ($$ == NULL) MYSQL_YYABORT; } | COLUMN_CHECK_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_dyncol_check($3); + $$= new (thd->mem_root) Item_func_dyncol_check($3); if ($$ == NULL) MYSQL_YYABORT; } | COLUMN_CREATE_SYM '(' dyncall_create_list ')' { - $$= create_func_dyncol_create(YYTHD, *$3); + $$= create_func_dyncol_create(thd, *$3); if ($$ == NULL) MYSQL_YYABORT; } @@ -9619,7 +9656,7 @@ function_call_nonkeyword: COLUMN_GET_SYM '(' expr ',' expr AS cast_type ')' { LEX *lex= Lex; - $$= create_func_dyncol_get(YYTHD, $3, $5, $7, + $$= create_func_dyncol_get(thd, $3, $5, $7, lex->length, lex->dec, lex->charset); if ($$ == NULL) @@ -9635,71 +9672,68 @@ function_call_nonkeyword: function_call_conflict: ASCII_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_ascii($3); + $$= new (thd->mem_root) Item_func_ascii($3); if ($$ == NULL) MYSQL_YYABORT; } | CHARSET '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_charset($3); + $$= new (thd->mem_root) Item_func_charset($3); if ($$ == NULL) MYSQL_YYABORT; } | COALESCE '(' expr_list ')' { - $$= new (YYTHD->mem_root) Item_func_coalesce(* $3); + $$= new (thd->mem_root) Item_func_coalesce(* $3); if ($$ == NULL) MYSQL_YYABORT; } | COLLATION_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_collation($3); + $$= new (thd->mem_root) Item_func_collation($3); if ($$ == NULL) MYSQL_YYABORT; } | DATABASE '(' ')' { - $$= new (YYTHD->mem_root) Item_func_database(); + $$= new (thd->mem_root) Item_func_database(); if ($$ == NULL) MYSQL_YYABORT; Lex->safe_to_cache_query=0; } | IF '(' expr ',' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_if($3,$5,$7); + $$= new (thd->mem_root) Item_func_if($3,$5,$7); if ($$ == NULL) MYSQL_YYABORT; } | LAST_VALUE '(' expr_list ')' { - $$= new (YYTHD->mem_root) Item_func_last_value(* $3); + $$= new (thd->mem_root) Item_func_last_value(* $3); if ($$ == NULL) MYSQL_YYABORT; } | MICROSECOND_SYM '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_microsecond($3); + $$= new (thd->mem_root) Item_func_microsecond($3); if ($$ == NULL) MYSQL_YYABORT; } | MOD_SYM '(' expr ',' expr ')' { - $$ = new (YYTHD->mem_root) Item_func_mod($3, $5); + $$ = new (thd->mem_root) Item_func_mod($3, $5); if ($$ == NULL) MYSQL_YYABORT; } | OLD_PASSWORD '(' expr ')' { - $$= new (YYTHD->mem_root) Item_func_old_password($3); - Lex->contains_plaintext_password= true; + $$= new (thd->mem_root) Item_func_old_password($3); if ($$ == NULL) MYSQL_YYABORT; } | PASSWORD '(' expr ')' { - THD *thd= YYTHD; Item* i1; - Lex->contains_plaintext_password= true; if (thd->variables.old_passwords == 1) i1= new (thd->mem_root) Item_func_old_password($3); else @@ -9710,25 +9744,31 @@ function_call_conflict: } | QUARTER_SYM '(' expr ')' { - $$ = new (YYTHD->mem_root) Item_func_quarter($3); + $$ = new (thd->mem_root) Item_func_quarter($3); if ($$ == NULL) MYSQL_YYABORT; } | REPEAT_SYM '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_repeat($3,$5); + $$= new (thd->mem_root) Item_func_repeat($3,$5); if ($$ == NULL) MYSQL_YYABORT; } | REPLACE '(' expr ',' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_replace($3,$5,$7); + $$= new (thd->mem_root) Item_func_replace($3,$5,$7); + if ($$ == NULL) + MYSQL_YYABORT; + } + | REVERSE_SYM '(' expr ')' + { + $$= new (thd->mem_root) Item_func_reverse($3); if ($$ == NULL) MYSQL_YYABORT; } | ROW_COUNT_SYM '(' ')' { - $$= new (YYTHD->mem_root) Item_func_row_count(); + $$= new (thd->mem_root) Item_func_row_count(); if ($$ == NULL) MYSQL_YYABORT; Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); @@ -9736,13 +9776,12 @@ function_call_conflict: } | TRUNCATE_SYM '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_round($3,$5,1); + $$= new (thd->mem_root) Item_func_round($3,$5,1); if ($$ == NULL) MYSQL_YYABORT; } | WEEK_SYM '(' expr ')' { - THD *thd= YYTHD; Item *i1= new (thd->mem_root) Item_int((char*) "0", thd->variables.default_week_format, 1); @@ -9754,7 +9793,37 @@ function_call_conflict: } | WEEK_SYM '(' expr ',' expr ')' { - $$= new (YYTHD->mem_root) Item_func_week($3,$5); + $$= new (thd->mem_root) Item_func_week($3,$5); + if ($$ == NULL) + MYSQL_YYABORT; + } + | WEIGHT_STRING_SYM '(' expr opt_ws_levels ')' + { + $$= new (thd->mem_root) Item_func_weight_string($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($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($3, $6, &my_charset_bin); + if (item == NULL) + MYSQL_YYABORT; + $$= new (thd->mem_root) + Item_func_weight_string(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($3, $5, $7, $9); if ($$ == NULL) MYSQL_YYABORT; } @@ -9776,52 +9845,52 @@ function_call_conflict: geometry_function: CONTAINS_SYM '(' expr ',' expr ')' { - $$= GEOM_NEW(YYTHD, + $$= GEOM_NEW(thd, Item_func_spatial_rel($3, $5, Item_func::SP_CONTAINS_FUNC)); } | GEOMETRYCOLLECTION '(' expr_list ')' { - $$= GEOM_NEW(YYTHD, + $$= GEOM_NEW(thd, Item_func_spatial_collection(* $3, Geometry::wkb_geometrycollection, Geometry::wkb_point)); } | LINESTRING '(' expr_list ')' { - $$= GEOM_NEW(YYTHD, + $$= GEOM_NEW(thd, Item_func_spatial_collection(* $3, Geometry::wkb_linestring, Geometry::wkb_point)); } | MULTILINESTRING '(' expr_list ')' { - $$= GEOM_NEW(YYTHD, + $$= GEOM_NEW(thd, Item_func_spatial_collection(* $3, Geometry::wkb_multilinestring, Geometry::wkb_linestring)); } | MULTIPOINT '(' expr_list ')' { - $$= GEOM_NEW(YYTHD, + $$= GEOM_NEW(thd, Item_func_spatial_collection(* $3, Geometry::wkb_multipoint, Geometry::wkb_point)); } | MULTIPOLYGON '(' expr_list ')' { - $$= GEOM_NEW(YYTHD, + $$= GEOM_NEW(thd, Item_func_spatial_collection(* $3, Geometry::wkb_multipolygon, Geometry::wkb_polygon)); } | POINT_SYM '(' expr ',' expr ')' { - $$= GEOM_NEW(YYTHD, Item_func_point($3,$5)); + $$= GEOM_NEW(thd, Item_func_point($3,$5)); } | POLYGON '(' expr_list ')' { - $$= GEOM_NEW(YYTHD, + $$= GEOM_NEW(thd, Item_func_spatial_collection(* $3, Geometry::wkb_polygon, Geometry::wkb_linestring)); @@ -9859,7 +9928,6 @@ function_call_generic: } opt_udf_expr_list ')' { - THD *thd= YYTHD; Create_func *builder; Item *item= NULL; @@ -9913,7 +9981,6 @@ function_call_generic: } | ident '.' ident '(' opt_expr_list ')' { - THD *thd= YYTHD; Create_qfunc *builder; Item *item= NULL; @@ -9977,7 +10044,7 @@ opt_udf_expr_list: udf_expr_list: udf_expr { - $$= new (YYTHD->mem_root) List<Item>; + $$= new (thd->mem_root) List<Item>; if ($$ == NULL) MYSQL_YYABORT; $$->push_back($1); @@ -10010,7 +10077,7 @@ udf_expr: remember_name we may get quoted or escaped names. */ else if ($2->type() != Item::FIELD_ITEM) - $2->set_name($1, (uint) ($3 - $1), YYTHD->charset()); + $2->set_name($1, (uint) ($3 - $1), thd->charset()); $$= $2; } ; @@ -10018,46 +10085,46 @@ udf_expr: sum_expr: AVG_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_avg($3, FALSE); + $$= new (thd->mem_root) Item_sum_avg($3, FALSE); if ($$ == NULL) MYSQL_YYABORT; } | AVG_SYM '(' DISTINCT in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_avg($4, TRUE); + $$= new (thd->mem_root) Item_sum_avg($4, TRUE); if ($$ == NULL) MYSQL_YYABORT; } | BIT_AND '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_and($3); + $$= new (thd->mem_root) Item_sum_and($3); if ($$ == NULL) MYSQL_YYABORT; } | BIT_OR '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_or($3); + $$= new (thd->mem_root) Item_sum_or($3); if ($$ == NULL) MYSQL_YYABORT; } | BIT_XOR '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_xor($3); + $$= new (thd->mem_root) Item_sum_xor($3); if ($$ == NULL) MYSQL_YYABORT; } | COUNT_SYM '(' opt_all '*' ')' { - Item *item= new (YYTHD->mem_root) Item_int((int32) 0L,1); + Item *item= new (thd->mem_root) Item_int((int32) 0L,1); if (item == NULL) MYSQL_YYABORT; - $$= new (YYTHD->mem_root) Item_sum_count(item); + $$= new (thd->mem_root) Item_sum_count(item); if ($$ == NULL) MYSQL_YYABORT; } | COUNT_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_count($3); + $$= new (thd->mem_root) Item_sum_count($3); if ($$ == NULL) MYSQL_YYABORT; } @@ -10067,13 +10134,13 @@ sum_expr: { Select->in_sum_expr--; } ')' { - $$= new (YYTHD->mem_root) Item_sum_count(* $5); + $$= new (thd->mem_root) Item_sum_count(* $5); if ($$ == NULL) MYSQL_YYABORT; } | MIN_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_min($3); + $$= new (thd->mem_root) Item_sum_min($3); if ($$ == NULL) MYSQL_YYABORT; } @@ -10084,55 +10151,55 @@ sum_expr: */ | MIN_SYM '(' DISTINCT in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_min($4); + $$= new (thd->mem_root) Item_sum_min($4); if ($$ == NULL) MYSQL_YYABORT; } | MAX_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_max($3); + $$= new (thd->mem_root) Item_sum_max($3); if ($$ == NULL) MYSQL_YYABORT; } | MAX_SYM '(' DISTINCT in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_max($4); + $$= new (thd->mem_root) Item_sum_max($4); if ($$ == NULL) MYSQL_YYABORT; } | STD_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_std($3, 0); + $$= new (thd->mem_root) Item_sum_std($3, 0); if ($$ == NULL) MYSQL_YYABORT; } | VARIANCE_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_variance($3, 0); + $$= new (thd->mem_root) Item_sum_variance($3, 0); if ($$ == NULL) MYSQL_YYABORT; } | STDDEV_SAMP_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_std($3, 1); + $$= new (thd->mem_root) Item_sum_std($3, 1); if ($$ == NULL) MYSQL_YYABORT; } | VAR_SAMP_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_variance($3, 1); + $$= new (thd->mem_root) Item_sum_variance($3, 1); if ($$ == NULL) MYSQL_YYABORT; } | SUM_SYM '(' in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_sum($3, FALSE); + $$= new (thd->mem_root) Item_sum_sum($3, FALSE); if ($$ == NULL) MYSQL_YYABORT; } | SUM_SYM '(' DISTINCT in_sum_expr ')' { - $$= new (YYTHD->mem_root) Item_sum_sum($4, TRUE); + $$= new (thd->mem_root) Item_sum_sum($4, TRUE); if ($$ == NULL) MYSQL_YYABORT; } @@ -10144,7 +10211,7 @@ sum_expr: { SELECT_LEX *sel= Select; sel->in_sum_expr--; - $$= new (YYTHD->mem_root) + $$= new (thd->mem_root) Item_func_group_concat(Lex->current_context(), $3, $5, sel->gorder_list, $7); if ($$ == NULL) @@ -10173,7 +10240,7 @@ variable_aux: ident_or_text SET_VAR expr { Item_func_set_user_var *item; - $$= item= new (YYTHD->mem_root) Item_func_set_user_var($1, $3); + $$= item= new (thd->mem_root) Item_func_set_user_var($1, $3); if ($$ == NULL) MYSQL_YYABORT; LEX *lex= Lex; @@ -10182,7 +10249,7 @@ variable_aux: } | ident_or_text { - $$= new (YYTHD->mem_root) Item_func_get_user_var($1); + $$= new (thd->mem_root) Item_func_get_user_var($1); if ($$ == NULL) MYSQL_YYABORT; LEX *lex= Lex; @@ -10196,7 +10263,7 @@ variable_aux: my_parse_error(ER(ER_SYNTAX_ERROR)); MYSQL_YYABORT; } - if (!($$= get_system_var(YYTHD, $2, $3, $4))) + if (!($$= get_system_var(thd, $2, $3, $4))) MYSQL_YYABORT; if (!((Item_func_get_system_var*) $$)->is_written_to_binlog()) Lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_VARIABLE); @@ -10211,7 +10278,7 @@ opt_distinct: opt_gconcat_separator: /* empty */ { - $$= new (YYTHD->mem_root) String(",", 1, &my_charset_latin1); + $$= new (thd->mem_root) String(",", 1, &my_charset_latin1); if ($$ == NULL) MYSQL_YYABORT; } @@ -10238,9 +10305,9 @@ opt_gorder_clause: gorder_list: gorder_list ',' order_ident order_dir - { if (add_gorder_to_list(YYTHD, $3,(bool) $4)) MYSQL_YYABORT; } + { if (add_gorder_to_list(thd, $3,(bool) $4)) MYSQL_YYABORT; } | order_ident order_dir - { if (add_gorder_to_list(YYTHD, $1,(bool) $2)) MYSQL_YYABORT; } + { if (add_gorder_to_list(thd, $1,(bool) $2)) MYSQL_YYABORT; } ; in_sum_expr: @@ -10306,7 +10373,7 @@ opt_expr_list: expr_list: expr { - $$= new (YYTHD->mem_root) List<Item>; + $$= new (thd->mem_root) List<Item>; if ($$ == NULL) MYSQL_YYABORT; $$->push_back($1); @@ -10326,7 +10393,7 @@ ident_list_arg: ident_list: simple_ident { - $$= new (YYTHD->mem_root) List<Item>; + $$= new (thd->mem_root) List<Item>; if ($$ == NULL) MYSQL_YYABORT; $$->push_back($1); @@ -10427,7 +10494,7 @@ join_table: { MYSQL_YYABORT_UNLESS($1 && $3); /* Change the current name resolution context to a local context. */ - if (push_new_name_resolution_context(YYTHD, $1, $3)) + if (push_new_name_resolution_context(thd, $1, $3)) MYSQL_YYABORT; Select->parsing_place= IN_ON; } @@ -10462,7 +10529,7 @@ join_table: { MYSQL_YYABORT_UNLESS($1 && $5); /* Change the current name resolution context to a local context. */ - if (push_new_name_resolution_context(YYTHD, $1, $5)) + if (push_new_name_resolution_context(thd, $1, $5)) MYSQL_YYABORT; Select->parsing_place= IN_ON; } @@ -10498,7 +10565,7 @@ join_table: { MYSQL_YYABORT_UNLESS($1 && $5); /* Change the current name resolution context to a local context. */ - if (push_new_name_resolution_context(YYTHD, $1, $5)) + if (push_new_name_resolution_context(thd, $1, $5)) MYSQL_YYABORT; Select->parsing_place= IN_ON; } @@ -10575,7 +10642,7 @@ table_factor: } table_ident opt_use_partition opt_table_alias opt_key_definition { - if (!($$= Select->add_table_to_list(YYTHD, $2, $4, + if (!($$= Select->add_table_to_list(thd, $2, $4, Select->get_table_join_options(), YYPS->m_lock_type, YYPS->m_mdl_type, @@ -10855,7 +10922,7 @@ opt_outer: index_hint_clause: /* empty */ { - $$= YYTHD->variables.old_mode ? INDEX_HINT_MASK_JOIN : INDEX_HINT_MASK_ALL; + $$= thd->variables.old_mode ? INDEX_HINT_MASK_JOIN : INDEX_HINT_MASK_ALL; } | FOR_SYM JOIN_SYM { $$= INDEX_HINT_MASK_JOIN; } | FOR_SYM ORDER_SYM BY { $$= INDEX_HINT_MASK_ORDER; } @@ -10887,7 +10954,7 @@ index_hints_list: opt_index_hints_list: /* empty */ - | { Select->alloc_index_hints(YYTHD); } index_hints_list + | { Select->alloc_index_hints(thd); } index_hints_list ; opt_key_definition: @@ -10896,15 +10963,15 @@ opt_key_definition: ; opt_key_usage_list: - /* empty */ { Select->add_index_hint(YYTHD, NULL, 0); } + /* empty */ { Select->add_index_hint(thd, NULL, 0); } | key_usage_list {} ; key_usage_element: ident - { Select->add_index_hint(YYTHD, $1.str, $1.length); } + { Select->add_index_hint(thd, $1.str, $1.length); } | PRIMARY_SYM - { Select->add_index_hint(YYTHD, (char *)"PRIMARY", 7); } + { Select->add_index_hint(thd, (char *)"PRIMARY", 7); } ; key_usage_list: @@ -10917,7 +10984,7 @@ using_list: { if (!($$= new List<String>)) MYSQL_YYABORT; - String *s= new (YYTHD->mem_root) String((const char *) $1.str, + String *s= new (thd->mem_root) String((const char *) $1.str, $1.length, system_charset_info); if (s == NULL) @@ -10926,7 +10993,7 @@ using_list: } | using_list ',' ident { - String *s= new (YYTHD->mem_root) String((const char *) $3.str, + String *s= new (thd->mem_root) String((const char *) $3.str, $3.length, system_charset_info); if (s == NULL) @@ -11031,7 +11098,6 @@ opt_escape: } | /* empty */ { - THD *thd= YYTHD; Lex->escape_used= FALSE; $$= ((thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) ? new (thd->mem_root) Item_string("", 0, &my_charset_latin1) : @@ -11052,9 +11118,9 @@ group_clause: group_list: group_list ',' order_ident order_dir - { if (add_group_to_list(YYTHD, $3,(bool) $4)) MYSQL_YYABORT; } + { if (add_group_to_list(thd, $3,(bool) $4)) MYSQL_YYABORT; } | order_ident order_dir - { if (add_group_to_list(YYTHD, $1,(bool) $2)) MYSQL_YYABORT; } + { if (add_group_to_list(thd, $1,(bool) $2)) MYSQL_YYABORT; } ; olap_opt: @@ -11115,7 +11181,6 @@ alter_order_list: alter_order_item: simple_ident_nospvar order_dir { - THD *thd= YYTHD; bool ascending= ($2 == 1) ? true : false; if (add_order_to_list(thd, $1, ascending)) MYSQL_YYABORT; @@ -11168,9 +11233,9 @@ order_clause: order_list: order_list ',' order_ident order_dir - { if (add_order_to_list(YYTHD, $3,(bool) $4)) MYSQL_YYABORT; } + { if (add_order_to_list(thd, $3,(bool) $4)) MYSQL_YYABORT; } | order_ident order_dir - { if (add_order_to_list(YYTHD, $1,(bool) $2)) MYSQL_YYABORT; } + { if (add_order_to_list(thd, $1,(bool) $2)) MYSQL_YYABORT; } ; order_dir: @@ -11239,7 +11304,6 @@ limit_option: ident { Item_splocal *splocal; - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= & thd->m_parser_state->m_lip; sp_variable *spv; @@ -11276,19 +11340,19 @@ limit_option: } | ULONGLONG_NUM { - $$= new (YYTHD->mem_root) Item_uint($1.str, $1.length); + $$= new (thd->mem_root) Item_uint($1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | LONG_NUM { - $$= new (YYTHD->mem_root) Item_uint($1.str, $1.length); + $$= new (thd->mem_root) Item_uint($1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | NUM { - $$= new (YYTHD->mem_root) Item_uint($1.str, $1.length); + $$= new (thd->mem_root) Item_uint($1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } @@ -11392,7 +11456,7 @@ procedure_clause: lex->proc_list.elements=0; lex->proc_list.first=0; lex->proc_list.next= &lex->proc_list.first; - Item_field *item= new (YYTHD->mem_root) + Item_field *item= new (thd->mem_root) Item_field(&lex->current_select->context, NULL, NULL, $2.str); if (item == NULL) @@ -11417,8 +11481,6 @@ procedure_list2: procedure_item: remember_name expr remember_end { - THD *thd= YYTHD; - if (add_proc_to_list(thd, $2)) MYSQL_YYABORT; if (!$2->name) @@ -11592,7 +11654,6 @@ drop: } | DROP FUNCTION_SYM opt_if_exists ident '.' ident { - THD *thd= YYTHD; LEX *lex= thd->lex; sp_name *spname; if ($4.str && check_db_name(&$4)) @@ -11615,7 +11676,6 @@ drop: } | DROP FUNCTION_SYM opt_if_exists ident { - THD *thd= YYTHD; LEX *lex= thd->lex; LEX_STRING db= {0, 0}; sp_name *spname; @@ -11650,6 +11710,10 @@ drop: { Lex->sql_command = SQLCOM_DROP_USER; } + | DROP ROLE_SYM clear_privileges role_list + { + Lex->sql_command = SQLCOM_DROP_ROLE; + } | DROP VIEW_SYM opt_if_exists { LEX *lex= Lex; @@ -11700,7 +11764,7 @@ table_list: table_name: table_ident { - if (!Select->add_table_to_list(YYTHD, $1, NULL, + if (!Select->add_table_to_list(thd, $1, NULL, TL_OPTION_UPDATING, YYPS->m_lock_type, YYPS->m_mdl_type)) @@ -11711,7 +11775,7 @@ table_name: table_name_with_opt_use_partition: table_ident opt_use_partition { - if (!Select->add_table_to_list(YYTHD, $1, NULL, + if (!Select->add_table_to_list(thd, $1, NULL, TL_OPTION_UPDATING, YYPS->m_lock_type, YYPS->m_mdl_type, @@ -11729,7 +11793,7 @@ table_alias_ref_list: table_alias_ref: table_ident_opt_wild { - if (!Select->add_table_to_list(YYTHD, $1, NULL, + if (!Select->add_table_to_list(thd, $1, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, YYPS->m_lock_type, YYPS->m_mdl_type)) @@ -11807,7 +11871,7 @@ insert_lock_option: | DELAYED_SYM { Lex->keyword_delayed_begin_offset= (uint)(YYLIP->get_tok_start() - - YYTHD->query()); + thd->query()); Lex->keyword_delayed_end_offset= Lex->keyword_delayed_begin_offset + YYLIP->yyLength() + 1; $$= TL_WRITE_DELAYED; @@ -11820,7 +11884,7 @@ replace_lock_option: | DELAYED_SYM { Lex->keyword_delayed_begin_offset= (uint)(YYLIP->get_tok_start() - - YYTHD->query()); + thd->query()); Lex->keyword_delayed_end_offset= Lex->keyword_delayed_begin_offset + YYLIP->yyLength() + 1; $$= TL_WRITE_DELAYED; @@ -11937,7 +12001,7 @@ expr_or_default: expr { $$= $1;} | DEFAULT { - $$= new (YYTHD->mem_root) Item_default_value(Lex->current_context()); + $$= new (thd->mem_root) Item_default_value(Lex->current_context()); if ($$ == NULL) MYSQL_YYABORT; } @@ -11990,7 +12054,7 @@ update_list: update_elem: simple_ident_nospvar equal expr_or_default { - if (add_item_to_list(YYTHD, $1) || add_value_to_list(YYTHD, $3)) + if (add_item_to_list(thd, $1) || add_value_to_list(thd, $3)) MYSQL_YYABORT; } ; @@ -12035,7 +12099,7 @@ delete: single_multi: FROM table_ident opt_use_partition { - if (!Select->add_table_to_list(YYTHD, $2, NULL, TL_OPTION_UPDATING, + if (!Select->add_table_to_list(thd, $2, NULL, TL_OPTION_UPDATING, YYPS->m_lock_type, YYPS->m_mdl_type, NULL, @@ -12046,6 +12110,7 @@ single_multi: } where_clause opt_order_clause delete_limit_clause {} + opt_select_expressions {} | table_wild_list { mysql_init_multi_delete(Lex); @@ -12070,6 +12135,11 @@ single_multi: } ; +opt_select_expressions: + /* empty */ + | RETURNING_SYM select_item_list + ; + table_wild_list: table_wild_one | table_wild_list ',' table_wild_one @@ -12081,7 +12151,7 @@ table_wild_one: Table_ident *ti= new Table_ident($1); if (ti == NULL) MYSQL_YYABORT; - if (!Select->add_table_to_list(YYTHD, + if (!Select->add_table_to_list(thd, ti, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, @@ -12091,10 +12161,10 @@ table_wild_one: } | ident '.' ident opt_wild { - Table_ident *ti= new Table_ident(YYTHD, $1, $3, 0); + Table_ident *ti= new Table_ident(thd, $1, $3, 0); if (ti == NULL) MYSQL_YYABORT; - if (!Select->add_table_to_list(YYTHD, + if (!Select->add_table_to_list(thd, ti, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, @@ -12134,7 +12204,6 @@ truncate: } table_name { - THD *thd= YYTHD; LEX* lex= thd->lex; DBUG_ASSERT(!lex->m_sql_cmd); lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_truncate_table(); @@ -12229,7 +12298,7 @@ show_param: { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_DATABASES; - if (prepare_schema_table(YYTHD, lex, 0, SCH_SCHEMATA)) + if (prepare_schema_table(thd, lex, 0, SCH_SCHEMATA)) MYSQL_YYABORT; } | opt_full TABLES opt_db wild_and_where @@ -12237,7 +12306,7 @@ show_param: LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_TABLES; lex->select_lex.db= $3; - if (prepare_schema_table(YYTHD, lex, 0, SCH_TABLE_NAMES)) + if (prepare_schema_table(thd, lex, 0, SCH_TABLE_NAMES)) MYSQL_YYABORT; } | opt_full TRIGGERS_SYM opt_db wild_and_where @@ -12245,7 +12314,7 @@ show_param: LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_TRIGGERS; lex->select_lex.db= $3; - if (prepare_schema_table(YYTHD, lex, 0, SCH_TRIGGERS)) + if (prepare_schema_table(thd, lex, 0, SCH_TRIGGERS)) MYSQL_YYABORT; } | EVENTS_SYM opt_db wild_and_where @@ -12253,7 +12322,7 @@ show_param: LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_EVENTS; lex->select_lex.db= $2; - if (prepare_schema_table(YYTHD, lex, 0, SCH_EVENTS)) + if (prepare_schema_table(thd, lex, 0, SCH_EVENTS)) MYSQL_YYABORT; } | TABLE_SYM STATUS_SYM opt_db wild_and_where @@ -12261,7 +12330,7 @@ show_param: LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_TABLE_STATUS; lex->select_lex.db= $3; - if (prepare_schema_table(YYTHD, lex, 0, SCH_TABLES)) + if (prepare_schema_table(thd, lex, 0, SCH_TABLES)) MYSQL_YYABORT; } | OPEN_SYM TABLES opt_db wild_and_where @@ -12269,27 +12338,27 @@ show_param: LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_OPEN_TABLES; lex->select_lex.db= $3; - if (prepare_schema_table(YYTHD, lex, 0, SCH_OPEN_TABLES)) + if (prepare_schema_table(thd, lex, 0, SCH_OPEN_TABLES)) MYSQL_YYABORT; } | PLUGINS_SYM { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_PLUGINS; - if (prepare_schema_table(YYTHD, lex, 0, SCH_PLUGINS)) + 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(YYTHD, Lex, 0, SCH_ALL_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(YYTHD, Lex, 0, SCH_ALL_PLUGINS)) + if (prepare_schema_table(thd, Lex, 0, SCH_ALL_PLUGINS)) MYSQL_YYABORT; } | ENGINE_SYM known_storage_engines show_engine_param @@ -12302,7 +12371,7 @@ show_param: lex->sql_command= SQLCOM_SHOW_FIELDS; if ($5) $4->change_db($5); - if (prepare_schema_table(YYTHD, lex, $4, SCH_COLUMNS)) + if (prepare_schema_table(thd, lex, $4, SCH_COLUMNS)) MYSQL_YYABORT; } | master_or_binary LOGS_SYM @@ -12329,33 +12398,25 @@ show_param: lex->sql_command= SQLCOM_SHOW_KEYS; if ($4) $3->change_db($4); - if (prepare_schema_table(YYTHD, lex, $3, SCH_STATISTICS)) + if (prepare_schema_table(thd, lex, $3, SCH_STATISTICS)) MYSQL_YYABORT; } | opt_storage ENGINES_SYM { LEX *lex=Lex; lex->sql_command= SQLCOM_SHOW_STORAGE_ENGINES; - if (prepare_schema_table(YYTHD, lex, 0, SCH_ENGINES)) + if (prepare_schema_table(thd, lex, 0, SCH_ENGINES)) MYSQL_YYABORT; } | AUTHORS_SYM { LEX *lex=Lex; lex->sql_command= SQLCOM_SHOW_AUTHORS; - push_warning_printf(YYTHD, Sql_condition::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(YYTHD, Sql_condition::WARN_LEVEL_WARN, - ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT, - ER(ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT), - "SHOW CONTRIBUTORS"); } | PRIVILEGES { @@ -12376,7 +12437,7 @@ show_param: { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_PROFILE; - if (prepare_schema_table(YYTHD, lex, NULL, SCH_PROFILES) != 0) + if (prepare_schema_table(thd, lex, NULL, SCH_PROFILES) != 0) YYABORT; } | opt_var_type STATUS_SYM wild_and_where @@ -12384,7 +12445,7 @@ show_param: LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_STATUS; lex->option_type= $1; - if (prepare_schema_table(YYTHD, lex, 0, SCH_STATUS)) + if (prepare_schema_table(thd, lex, 0, SCH_STATUS)) MYSQL_YYABORT; } | opt_full PROCESSLIST_SYM @@ -12394,39 +12455,35 @@ show_param: LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_VARIABLES; lex->option_type= $1; - if (prepare_schema_table(YYTHD, lex, 0, SCH_VARIABLES)) + if (prepare_schema_table(thd, lex, 0, SCH_VARIABLES)) MYSQL_YYABORT; } | charset wild_and_where { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_CHARSETS; - if (prepare_schema_table(YYTHD, lex, 0, SCH_CHARSETS)) + if (prepare_schema_table(thd, lex, 0, SCH_CHARSETS)) MYSQL_YYABORT; } | COLLATION_SYM wild_and_where { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_COLLATIONS; - if (prepare_schema_table(YYTHD, lex, 0, SCH_COLLATIONS)) + if (prepare_schema_table(thd, lex, 0, SCH_COLLATIONS)) MYSQL_YYABORT; } | 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 { 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 { @@ -12438,7 +12495,7 @@ show_param: { LEX *lex= Lex; lex->sql_command = SQLCOM_SHOW_CREATE; - if (!lex->select_lex.add_table_to_list(YYTHD, $3, NULL,0)) + if (!lex->select_lex.add_table_to_list(thd, $3, NULL,0)) MYSQL_YYABORT; lex->only_view= 0; lex->create_info.storage_media= HA_SM_DEFAULT; @@ -12447,7 +12504,7 @@ show_param: { LEX *lex= Lex; lex->sql_command = SQLCOM_SHOW_CREATE; - if (!lex->select_lex.add_table_to_list(YYTHD, $3, NULL, 0)) + if (!lex->select_lex.add_table_to_list(thd, $3, NULL, 0)) MYSQL_YYABORT; lex->only_view= 1; } @@ -12462,7 +12519,6 @@ show_param: } | SLAVE STATUS_SYM { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->mi.connection_name= thd->variables.default_master_connection; lex->sql_command = SQLCOM_SHOW_SLAVE_STAT; @@ -12477,28 +12533,28 @@ show_param: { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_CLIENT_STATS; - if (prepare_schema_table(YYTHD, lex, 0, SCH_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(YYTHD, lex, 0, SCH_USER_STATS)) + if (prepare_schema_table(thd, lex, 0, SCH_USER_STATS)) MYSQL_YYABORT; } | TABLE_STATS_SYM { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_TABLE_STATS; - if (prepare_schema_table(YYTHD, lex, 0, SCH_TABLE_STATS)) + if (prepare_schema_table(thd, lex, 0, SCH_TABLE_STATS)) MYSQL_YYABORT; } | INDEX_STATS_SYM { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_INDEX_STATS; - if (prepare_schema_table(YYTHD, lex, 0, SCH_INDEX_STATS)) + if (prepare_schema_table(thd, lex, 0, SCH_INDEX_STATS)) MYSQL_YYABORT; } | CREATE PROCEDURE_SYM sp_name @@ -12525,14 +12581,14 @@ show_param: { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_STATUS_PROC; - if (prepare_schema_table(YYTHD, lex, 0, SCH_PROCEDURES)) + if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES)) MYSQL_YYABORT; } | FUNCTION_SYM STATUS_SYM wild_and_where { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_STATUS_FUNC; - if (prepare_schema_table(YYTHD, lex, 0, SCH_PROCEDURES)) + if (prepare_schema_table(thd, lex, 0, SCH_PROCEDURES)) MYSQL_YYABORT; } | PROCEDURE_SYM CODE_SYM sp_name @@ -12552,7 +12608,6 @@ show_param: } | describe_command FOR_SYM expr { - THD *thd= YYTHD; Lex->sql_command= SQLCOM_SHOW_EXPLAIN; if (prepare_schema_table(thd, Lex, 0, SCH_EXPLAIN)) MYSQL_YYABORT; @@ -12608,7 +12663,7 @@ wild_and_where: /* empty */ | LIKE TEXT_STRING_sys { - Lex->wild= new (YYTHD->mem_root) String($2.str, $2.length, + Lex->wild= new (thd->mem_root) String($2.str, $2.length, system_charset_info); if (Lex->wild == NULL) MYSQL_YYABORT; @@ -12631,7 +12686,7 @@ describe: lex->sql_command= SQLCOM_SHOW_FIELDS; lex->select_lex.db= 0; lex->verbose= 0; - if (prepare_schema_table(YYTHD, lex, $2, SCH_COLUMNS)) + if (prepare_schema_table(thd, lex, $2, SCH_COLUMNS)) MYSQL_YYABORT; } opt_describe_column @@ -12640,13 +12695,21 @@ 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 @@ -12663,7 +12726,7 @@ opt_describe_column: | text_string { Lex->wild= $1; } | ident { - Lex->wild= new (YYTHD->mem_root) String((const char*) $1.str, + Lex->wild= new (thd->mem_root) String((const char*) $1.str, $1.length, system_charset_info); if (Lex->wild == NULL) @@ -12861,6 +12924,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 { @@ -12877,13 +12941,17 @@ 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; } | USER user { @@ -12913,7 +12981,6 @@ use: load: LOAD data_or_xml { - THD *thd= YYTHD; LEX *lex= thd->lex; if (lex->sphead) @@ -12936,7 +13003,7 @@ load: opt_duplicate INTO TABLE_SYM table_ident opt_use_partition { LEX *lex=Lex; - if (!Select->add_table_to_list(YYTHD, $12, NULL, TL_OPTION_UPDATING, + if (!Select->add_table_to_list(thd, $12, NULL, TL_OPTION_UPDATING, $4, MDL_SHARED_WRITE, NULL, $13)) MYSQL_YYABORT; lex->field_list.empty(); @@ -13075,7 +13142,7 @@ field_or_var: simple_ident_nospvar {$$= $1;} | '@' ident_or_text { - $$= new (YYTHD->mem_root) Item_user_var_as_out_param($2); + $$= new (thd->mem_root) Item_user_var_as_out_param($2); if ($$ == NULL) MYSQL_YYABORT; } @@ -13098,7 +13165,7 @@ load_data_set_elem: if (lex->update_list.push_back($1) || lex->value_list.push_back($4)) MYSQL_YYABORT; - $4->set_name_no_truncate($3, (uint) ($5 - $3), YYTHD->charset()); + $4->set_name_no_truncate($3, (uint) ($5 - $3), thd->charset()); } ; @@ -13108,7 +13175,6 @@ text_literal: TEXT_STRING { LEX_STRING tmp; - THD *thd= YYTHD; CHARSET_INFO *cs_con= thd->variables.collation_connection; CHARSET_INFO *cs_cli= thd->variables.character_set_client; uint repertoire= thd->lex->text_string_is_7bit && @@ -13134,7 +13200,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 (YYTHD->mem_root) Item_string($1.str, $1.length, + $$= new (thd->mem_root) Item_string($1.str, $1.length, national_charset_info, DERIVATION_COERCIBLE, repertoire); @@ -13143,7 +13209,7 @@ text_literal: } | UNDERSCORE_CHARSET TEXT_STRING { - Item_string *str= new (YYTHD->mem_root) Item_string($2.str, + Item_string *str= new (thd->mem_root) Item_string($2.str, $2.length, $1); if (str == NULL) MYSQL_YYABORT; @@ -13162,7 +13228,7 @@ text_literal: If the string has been pure ASCII so far, check the new part. */ - CHARSET_INFO *cs= YYTHD->variables.collation_connection; + CHARSET_INFO *cs= thd->variables.collation_connection; item->collation.repertoire|= my_string_repertoire(cs, $2.str, $2.length); @@ -13173,15 +13239,15 @@ text_literal: text_string: TEXT_STRING_literal { - $$= new (YYTHD->mem_root) String($1.str, + $$= new (thd->mem_root) String($1.str, $1.length, - YYTHD->variables.collation_connection); + thd->variables.collation_connection); if ($$ == NULL) MYSQL_YYABORT; } | HEX_NUM { - Item *tmp= new (YYTHD->mem_root) Item_hex_hybrid($1.str, $1.length); + Item *tmp= new (thd->mem_root) Item_hex_hybrid($1.str, $1.length); if (tmp == NULL) MYSQL_YYABORT; /* @@ -13193,7 +13259,7 @@ text_string: } | HEX_STRING { - Item *tmp= new (YYTHD->mem_root) Item_hex_string($1.str, $1.length); + Item *tmp= new (thd->mem_root) Item_hex_string($1.str, $1.length); if (tmp == NULL) MYSQL_YYABORT; tmp->quick_fix_field(); @@ -13201,7 +13267,7 @@ text_string: } | BIN_NUM { - Item *tmp= new (YYTHD->mem_root) Item_bin_string($1.str, $1.length); + Item *tmp= new (thd->mem_root) Item_bin_string($1.str, $1.length); if (tmp == NULL) MYSQL_YYABORT; /* @@ -13216,7 +13282,6 @@ text_string: param_marker: PARAM_MARKER { - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; Item_param *item; @@ -13250,44 +13315,44 @@ literal: | temporal_literal { $$= $1; } | NULL_SYM { - $$ = new (YYTHD->mem_root) Item_null(); + $$ = new (thd->mem_root) Item_null(); if ($$ == NULL) MYSQL_YYABORT; YYLIP->next_state= MY_LEX_OPERATOR_OR_IDENT; } | FALSE_SYM { - $$= new (YYTHD->mem_root) Item_int((char*) "FALSE",0,1); + $$= new (thd->mem_root) Item_int((char*) "FALSE",0,1); if ($$ == NULL) MYSQL_YYABORT; } | TRUE_SYM { - $$= new (YYTHD->mem_root) Item_int((char*) "TRUE",1,1); + $$= new (thd->mem_root) Item_int((char*) "TRUE",1,1); if ($$ == NULL) MYSQL_YYABORT; } | HEX_NUM { - $$ = new (YYTHD->mem_root) Item_hex_hybrid($1.str, $1.length); + $$ = new (thd->mem_root) Item_hex_hybrid($1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | HEX_STRING { - $$ = new (YYTHD->mem_root) Item_hex_string($1.str, $1.length); + $$ = new (thd->mem_root) Item_hex_string($1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | BIN_NUM { - $$= new (YYTHD->mem_root) Item_bin_string($1.str, $1.length); + $$= new (thd->mem_root) Item_bin_string($1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | UNDERSCORE_CHARSET hex_num_or_string { - Item *tmp= new (YYTHD->mem_root) Item_hex_string($2.str, $2.length); + Item *tmp= new (thd->mem_root) Item_hex_string($2.str, $2.length); if (tmp == NULL) MYSQL_YYABORT; /* @@ -13298,7 +13363,7 @@ literal: String *str= tmp->val_str((String*) 0); Item_string *item_str; - item_str= new (YYTHD->mem_root) + item_str= new (thd->mem_root) Item_string(NULL, /* name will be set in select_item */ str ? str->ptr() : "", str ? str->length() : 0, @@ -13316,7 +13381,7 @@ literal: } | UNDERSCORE_CHARSET BIN_NUM { - Item *tmp= new (YYTHD->mem_root) Item_bin_string($2.str, $2.length); + Item *tmp= new (thd->mem_root) Item_bin_string($2.str, $2.length); if (tmp == NULL) MYSQL_YYABORT; /* @@ -13327,7 +13392,7 @@ literal: String *str= tmp->val_str((String*) 0); Item_string *item_str; - item_str= new (YYTHD->mem_root) + item_str= new (thd->mem_root) Item_string(NULL, /* name will be set in select_item */ str ? str->ptr() : "", str ? str->length() : 0, @@ -13348,7 +13413,7 @@ NUM_literal: NUM { int error; - $$= new (YYTHD->mem_root) + $$= new (thd->mem_root) Item_int($1.str, (longlong) my_strtoll10($1.str, NULL, &error), $1.length); @@ -13358,7 +13423,7 @@ NUM_literal: | LONG_NUM { int error; - $$= new (YYTHD->mem_root) + $$= new (thd->mem_root) Item_int($1.str, (longlong) my_strtoll10($1.str, NULL, &error), $1.length); @@ -13367,23 +13432,23 @@ NUM_literal: } | ULONGLONG_NUM { - $$= new (YYTHD->mem_root) Item_uint($1.str, $1.length); + $$= new (thd->mem_root) Item_uint($1.str, $1.length); if ($$ == NULL) MYSQL_YYABORT; } | DECIMAL_NUM { - $$= new (YYTHD->mem_root) Item_decimal($1.str, $1.length, - YYTHD->charset()); - if (($$ == NULL) || (YYTHD->is_error())) + $$= new (thd->mem_root) Item_decimal($1.str, $1.length, + thd->charset()); + if (($$ == NULL) || (thd->is_error())) { MYSQL_YYABORT; } } | FLOAT_NUM { - $$= new (YYTHD->mem_root) Item_float($1.str, $1.length); - if (($$ == NULL) || (YYTHD->is_error())) + $$= new (thd->mem_root) Item_float($1.str, $1.length); + if (($$ == NULL) || (thd->is_error())) { MYSQL_YYABORT; } @@ -13394,19 +13459,19 @@ NUM_literal: temporal_literal: DATE_SYM TEXT_STRING { - if (!($$= create_temporal_literal(YYTHD, $2.str, $2.length, YYCSCL, + if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL, MYSQL_TYPE_DATE, true))) MYSQL_YYABORT; } | TIME_SYM TEXT_STRING { - if (!($$= create_temporal_literal(YYTHD, $2.str, $2.length, YYCSCL, + if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL, MYSQL_TYPE_TIME, true))) MYSQL_YYABORT; } | TIMESTAMP TEXT_STRING { - if (!($$= create_temporal_literal(YYTHD, $2.str, $2.length, YYCSCL, + if (!($$= create_temporal_literal(thd, $2.str, $2.length, YYCSCL, MYSQL_TYPE_DATETIME, true))) MYSQL_YYABORT; } @@ -13428,7 +13493,7 @@ table_wild: ident '.' '*' { SELECT_LEX *sel= Select; - $$= new (YYTHD->mem_root) Item_field(Lex->current_context(), + $$= new (thd->mem_root) Item_field(Lex->current_context(), NullS, $1.str, "*"); if ($$ == NULL) MYSQL_YYABORT; @@ -13436,7 +13501,6 @@ table_wild: } | ident '.' ident '.' '*' { - THD *thd= YYTHD; SELECT_LEX *sel= Select; const char* schema= thd->client_capabilities & CLIENT_NO_SCHEMA ? NullS : $1.str; @@ -13456,7 +13520,6 @@ order_ident: simple_ident: ident { - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; sp_variable *spv; @@ -13507,7 +13570,6 @@ simple_ident: simple_ident_nospvar: ident { - THD *thd= YYTHD; SELECT_LEX *sel=Select; if ((sel->parsing_place != IN_HAVING) || (sel->get_in_sum_expr() > 0)) @@ -13529,7 +13591,6 @@ simple_ident_nospvar: simple_ident_q: ident '.' ident { - THD *thd= YYTHD; LEX *lex= thd->lex; /* @@ -13608,7 +13669,6 @@ simple_ident_q: } | '.' ident '.' ident { - THD *thd= YYTHD; LEX *lex= thd->lex; SELECT_LEX *sel= lex->current_select; if (sel->no_table_names_allowed) @@ -13633,7 +13693,6 @@ simple_ident_q: } | ident '.' ident '.' ident { - THD *thd= YYTHD; LEX *lex= thd->lex; SELECT_LEX *sel= lex->current_select; const char* schema= (thd->client_capabilities & CLIENT_NO_SCHEMA ? @@ -13701,7 +13760,7 @@ table_ident: } | ident '.' ident { - $$= new Table_ident(YYTHD, $1,$3,0); + $$= new Table_ident(thd, $1,$3,0); if ($$ == NULL) MYSQL_YYABORT; } @@ -13723,7 +13782,7 @@ table_ident_opt_wild: } | ident '.' ident opt_wild { - $$= new Table_ident(YYTHD, $1,$3,0); + $$= new Table_ident(thd, $1,$3,0); if ($$ == NULL) MYSQL_YYABORT; } @@ -13733,7 +13792,7 @@ table_ident_nodb: ident { LEX_STRING db={(char*) any_db,3}; - $$= new Table_ident(YYTHD, db,$1,0); + $$= new Table_ident(thd, db,$1,0); if ($$ == NULL) MYSQL_YYABORT; } @@ -13743,8 +13802,6 @@ IDENT_sys: IDENT { $$= $1; } | IDENT_QUOTED { - THD *thd= YYTHD; - if (thd->charset_is_system_charset) { CHARSET_INFO *cs= system_charset_info; @@ -13773,8 +13830,6 @@ IDENT_sys: TEXT_STRING_sys: TEXT_STRING { - THD *thd= YYTHD; - if (thd->charset_is_system_charset) $$= $1; else @@ -13789,8 +13844,6 @@ TEXT_STRING_sys: TEXT_STRING_literal: TEXT_STRING { - THD *thd= YYTHD; - if (thd->charset_is_collation_connection) $$= $1; else @@ -13805,8 +13858,6 @@ TEXT_STRING_literal: TEXT_STRING_filesystem: TEXT_STRING { - THD *thd= YYTHD; - if (thd->charset_is_character_set_filesystem) $$= $1; else @@ -13823,7 +13874,6 @@ ident: IDENT_sys { $$=$1; } | keyword { - THD *thd= YYTHD; $$.str= thd->strmake($1.str, $1.length); if ($$.str == NULL) MYSQL_YYABORT; @@ -13835,7 +13885,6 @@ label_ident: IDENT_sys { $$=$1; } | keyword_sp { - THD *thd= YYTHD; $$.str= thd->strmake($1.str, $1.length); if ($$.str == NULL) MYSQL_YYABORT; @@ -13852,12 +13901,10 @@ ident_or_text: user: ident_or_text { - THD *thd= YYTHD; if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) MYSQL_YYABORT; $$->user = $1; - $$->host.str= (char *) "%"; - $$->host.length= 1; + $$->host= null_lex_str; // User or Role, see get_current_user() $$->password= null_lex_str; $$->plugin= empty_lex_str; $$->auth= empty_lex_str; @@ -13869,7 +13916,6 @@ user: } | ident_or_text '@' ident_or_text { - THD *thd= YYTHD; if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) MYSQL_YYABORT; $$->user = $1; $$->host=$3; @@ -13882,26 +13928,36 @@ user: 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*) YYTHD->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 | current_role; + /* Keyword that we allow for identifiers (except SP labels) */ keyword: keyword_sp {} @@ -13949,6 +14005,7 @@ keyword: | SAVEPOINT_SYM {} | SECURITY_SYM {} | SERVER_SYM {} + | SHUTDOWN {} | SIGNED_SYM {} | SOCKET_SYM {} | SLAVE {} @@ -13973,6 +14030,7 @@ keyword: keyword_sp: ACTION {} | ADDDATE_SYM {} + | ADMIN_SYM {} | AFTER_SYM {} | AGAINST {} | AGGREGATE_SYM {} @@ -14075,6 +14133,7 @@ keyword_sp: | HARD_SYM {} | HOSTS_SYM {} | HOUR_SYM {} + | ID_SYM {} | IDENTIFIED_SYM {} | IGNORE_SERVER_IDS_SYM {} | INDEX_STATS_SYM {} @@ -14200,6 +14259,8 @@ keyword_sp: | RESUME_SYM {} | RETURNED_SQLSTATE_SYM {} | RETURNS_SYM {} + | REVERSE_SYM {} + | ROLE_SYM {} | ROLLUP_SYM {} | ROUTINE_SYM {} | ROWS_SYM {} @@ -14215,7 +14276,6 @@ keyword_sp: | SESSION_SYM {} | SIMPLE_SYM {} | SHARE_SYM {} - | SHUTDOWN {} | SLAVE_POS_SYM {} | SLOW {} | SNAPSHOT_SYM {} @@ -14275,6 +14335,7 @@ keyword_sp: | WARNINGS {} | WAIT_SYM {} | WEEK_SYM {} + | WEIGHT_STRING_SYM {} | WORK_SYM {} | X509_SYM {} | XML_SYM {} @@ -14299,7 +14360,7 @@ set: lex->var_list.empty(); lex->one_shot_set= 0; lex->autocommit= 0; - sp_create_assignment_lex(YYTHD, yychar == YYEMPTY); + sp_create_assignment_lex(thd, yychar == YYEMPTY); } start_option_value_list {} @@ -14310,7 +14371,7 @@ set: start_option_value_list: option_value_no_option_type { - if (sp_create_assignment_instr(YYTHD, yychar == YYEMPTY)) + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) MYSQL_YYABORT; } option_value_list_continued @@ -14320,7 +14381,7 @@ start_option_value_list: } transaction_characteristics { - if (sp_create_assignment_instr(YYTHD, yychar == YYEMPTY)) + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) MYSQL_YYABORT; } | option_type @@ -14335,13 +14396,13 @@ start_option_value_list: start_option_value_list_following_option_type: option_value_following_option_type { - if (sp_create_assignment_instr(YYTHD, yychar == YYEMPTY)) + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) MYSQL_YYABORT; } option_value_list_continued | TRANSACTION_SYM transaction_characteristics { - if (sp_create_assignment_instr(YYTHD, yychar == YYEMPTY)) + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) MYSQL_YYABORT; } ; @@ -14355,20 +14416,20 @@ option_value_list_continued: // Repeating list of option values after first option value. option_value_list: { - sp_create_assignment_lex(YYTHD, yychar == YYEMPTY); + sp_create_assignment_lex(thd, yychar == YYEMPTY); } option_value { - if (sp_create_assignment_instr(YYTHD, yychar == YYEMPTY)) + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) MYSQL_YYABORT; } | option_value_list ',' { - sp_create_assignment_lex(YYTHD, yychar == YYEMPTY); + sp_create_assignment_lex(thd, yychar == YYEMPTY); } option_value { - if (sp_create_assignment_instr(YYTHD, yychar == YYEMPTY)) + if (sp_create_assignment_instr(thd, yychar == YYEMPTY)) MYSQL_YYABORT; } ; @@ -14407,7 +14468,6 @@ opt_var_ident_type: option_value_following_option_type: internal_variable_name equal set_expr_or_default { - THD *thd= YYTHD; LEX *lex= Lex; if ($1.var && $1.var != trg_new_row_fake_var) @@ -14432,13 +14492,12 @@ option_value_following_option_type: option_value_no_option_type: internal_variable_name equal set_expr_or_default { - THD *thd= YYTHD; LEX *lex= Lex; if ($1.var == trg_new_row_fake_var) { /* We are in trigger and assigning value to field of new row */ - if (set_trigger_new_row(YYTHD, &$1.base_name, $3)) + if (set_trigger_new_row(thd, &$1.base_name, $3)) MYSQL_YYABORT; } else if ($1.var) @@ -14460,7 +14519,7 @@ option_value_no_option_type: | '@' ident_or_text equal expr { Item_func_set_user_var *item; - item= new (YYTHD->mem_root) Item_func_set_user_var($2, $4); + item= new (thd->mem_root) Item_func_set_user_var($2, $4); if (item == NULL) MYSQL_YYABORT; set_var_user *var= new set_var_user(item); @@ -14470,7 +14529,6 @@ option_value_no_option_type: } | '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default { - THD *thd= YYTHD; struct sys_var_with_base tmp= $4; /* Lookup if necessary: must be a system variable. */ if (tmp.var == NULL) @@ -14483,7 +14541,6 @@ option_value_no_option_type: } | charset old_or_new_charset_name_or_default { - THD *thd= YYTHD; LEX *lex= thd->lex; CHARSET_INFO *cs2; cs2= $2 ? $2: global_system_variables.character_set_client; @@ -14529,9 +14586,14 @@ option_value_no_option_type: MYSQL_YYABORT; lex->var_list.push_back(var); } + | ROLE_SYM ident_or_text + { + LEX *lex = Lex; + set_var_role *var= new set_var_role($2); + lex->var_list.push_back(var); + } | PASSWORD equal text_or_password { - THD *thd= YYTHD; LEX *lex= thd->lex; LEX_USER *user; sp_pcontext *spc= lex->spcont; @@ -14544,11 +14606,9 @@ option_value_no_option_type: my_error(ER_SP_BAD_VAR_SHADOW, MYF(0), pw.str); MYSQL_YYABORT; } - if (!(user=(LEX_USER*) thd->alloc(sizeof(LEX_USER)))) + if (!(user=(LEX_USER*) thd->calloc(sizeof(LEX_USER)))) MYSQL_YYABORT; - user->host=null_lex_str; - user->user.str=thd->security_ctx->user; - user->user.length= strlen(thd->security_ctx->user); + user->user= current_user; set_var_password *var= new set_var_password(user, $3); if (var == NULL) MYSQL_YYABORT; @@ -14572,7 +14632,6 @@ option_value_no_option_type: internal_variable_name: ident { - THD *thd= YYTHD; sp_pcontext *spc= thd->lex->spcont; sp_variable *spv; @@ -14631,7 +14690,7 @@ internal_variable_name: } else { - sys_var *tmp=find_sys_var(YYTHD, $3.str, $3.length); + sys_var *tmp=find_sys_var(thd, $3.str, $3.length); if (!tmp) MYSQL_YYABORT; if (!tmp->is_struct()) @@ -14642,7 +14701,7 @@ internal_variable_name: } | DEFAULT '.' ident { - sys_var *tmp=find_sys_var(YYTHD, $3.str, $3.length); + sys_var *tmp=find_sys_var(thd, $3.str, $3.length); if (!tmp) MYSQL_YYABORT; if (!tmp->is_struct()) @@ -14663,7 +14722,6 @@ transaction_characteristics: transaction_access_mode: transaction_access_mode_types { - THD *thd= YYTHD; LEX *lex=Lex; Item *item= new (thd->mem_root) Item_int((int32) $1); if (item == NULL) @@ -14681,7 +14739,6 @@ transaction_access_mode: isolation_level: ISOLATION LEVEL_SYM isolation_types { - THD *thd= YYTHD; LEX *lex=Lex; Item *item= new (thd->mem_root) Item_int((int32) $3); if (item == NULL) @@ -14715,27 +14772,25 @@ text_or_password: if ($3.length == 0) $$= $3.str; else - switch (YYTHD->variables.old_passwords) { + switch (thd->variables.old_passwords) { case 1: $$= Item_func_old_password:: - alloc(YYTHD, $3.str, $3.length); + alloc(thd, $3.str, $3.length); break; case 0: case 2: $$= Item_func_password:: - create_password_hash_buffer(YYTHD, $3.str, $3.length); + create_password_hash_buffer(thd, $3.str, $3.length); break; } if ($$ == NULL) MYSQL_YYABORT; - Lex->contains_plaintext_password= true; } | OLD_PASSWORD '(' TEXT_STRING ')' { $$= $3.length ? Item_func_old_password:: - alloc(YYTHD, $3.str, $3.length) : + alloc(thd, $3.str, $3.length) : $3.str; if ($$ == NULL) MYSQL_YYABORT; - Lex->contains_plaintext_password= true; } ; @@ -14744,19 +14799,19 @@ set_expr_or_default: | DEFAULT { $$=0; } | ON { - $$=new (YYTHD->mem_root) Item_string("ON", 2, system_charset_info); + $$=new (thd->mem_root) Item_string("ON", 2, system_charset_info); if ($$ == NULL) MYSQL_YYABORT; } | ALL { - $$=new (YYTHD->mem_root) Item_string("ALL", 3, system_charset_info); + $$=new (thd->mem_root) Item_string("ALL", 3, system_charset_info); if ($$ == NULL) MYSQL_YYABORT; } | BINARY { - $$=new (YYTHD->mem_root) Item_string("binary", 6, system_charset_info); + $$=new (thd->mem_root) Item_string("binary", 6, system_charset_info); if ($$ == NULL) MYSQL_YYABORT; } @@ -14795,7 +14850,7 @@ table_lock: { thr_lock_type lock_type= (thr_lock_type) $3; bool lock_for_write= (lock_type >= TL_WRITE_ALLOW_WRITE); - if (!Select->add_table_to_list(YYTHD, $1, $2, 0, lock_type, + if (!Select->add_table_to_list(thd, $1, $2, 0, lock_type, (lock_for_write ? lock_type == TL_WRITE_CONCURRENT_INSERT ? MDL_SHARED_WRITE : @@ -14873,7 +14928,7 @@ 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 (YYTHD->mem_root) Item_int((int32) 1); + Item *one= new (thd->mem_root) Item_int((int32) 1); if (one == NULL) MYSQL_YYABORT; lex->current_select->select_limit= one; @@ -14938,13 +14993,13 @@ 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) @@ -14955,7 +15010,7 @@ revoke_command: 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) @@ -14966,7 +15021,7 @@ revoke_command: 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; } @@ -14976,9 +15031,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)) + 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 {} @@ -15022,7 +15090,67 @@ 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)) + 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)) + MYSQL_YYABORT; + } + | role_list ',' grant_role + { + if (Lex->users_list.push_back($3)) + MYSQL_YYABORT; + } + ; + +current_role: + CURRENT_ROLE optional_braces + { + if (!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER)))) + MYSQL_YYABORT; + $$->user= current_role; + $$->plugin= empty_lex_str; + $$->auth= empty_lex_str; + } + ; + +grant_role: + ident_or_text + { + if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) + MYSQL_YYABORT; + $$->user = $1; + $$->host= empty_lex_str; + $$->password= null_lex_str; + $$->plugin= empty_lex_str; + $$->auth= empty_lex_str; + + if (check_string_char_length(&$$->user, ER(ER_USERNAME), + username_char_length, + system_charset_info, 0)) + MYSQL_YYABORT; + } + | current_role ; opt_table: @@ -15212,6 +15340,19 @@ grant_list: } ; +user_and_role_list: + user_or_role + { + if (Lex->users_list.push_back($1)) + MYSQL_YYABORT; + } + | user_and_role_list ',' user_or_role + { + if (Lex->users_list.push_back($3)) + MYSQL_YYABORT; + } + ; + via_or_with: VIA_SYM | WITH ; using_or_as: USING | AS ; @@ -15223,10 +15364,10 @@ grant_user: MYSQL_YYABORT; if ($4.length) { - if (YYTHD->variables.old_passwords == 1) + if (thd->variables.old_passwords == 1) { char *buff= - (char *) YYTHD->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1); + (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1); if (buff == NULL) MYSQL_YYABORT; my_make_scrambled_password_323(buff, $4.str, $4.length); @@ -15236,7 +15377,7 @@ grant_user: else { char *buff= - (char *) YYTHD->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1); + (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1); if (buff == NULL) MYSQL_YYABORT; my_make_scrambled_password_sha1(buff, $4.str, $4.length); @@ -15262,7 +15403,7 @@ grant_user: $1->plugin= $4; $1->auth= $6; } - | user + | user_or_role { $$= $1; $1->password= null_lex_str; } ; @@ -15283,7 +15424,7 @@ column_list: column_list_id: ident { - String *new_str = new (YYTHD->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); @@ -15493,7 +15634,6 @@ opt_union_order_or_limit: union_order_or_limit: { - THD *thd= YYTHD; LEX *lex= thd->lex; DBUG_ASSERT(lex->current_select->linkage != GLOBAL_OPTIONS_TYPE); SELECT_LEX *sel= lex->current_select; @@ -15509,7 +15649,6 @@ union_order_or_limit: } order_or_limit { - THD *thd= YYTHD; thd->lex->current_select->no_table_names_allowed= 0; thd->where= ""; } @@ -15691,14 +15830,14 @@ no_definer: from older master servers (i.e. to create non-suid trigger in this case). */ - YYTHD->lex->definer= 0; + thd->lex->definer= 0; } ; definer: - DEFINER_SYM EQ user + DEFINER_SYM EQ user_or_role { - YYTHD->lex->definer= get_current_user(YYTHD, $3); + thd->lex->definer= $3; } ; @@ -15743,7 +15882,6 @@ view_suid: view_tail: view_suid VIEW_SYM table_ident { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->sql_command= SQLCOM_CREATE_VIEW; /* first table in list is target VIEW name */ @@ -15787,7 +15925,6 @@ view_select: } view_select_aux view_check_option { - THD *thd= YYTHD; LEX *lex= Lex; uint len= YYLIP->get_cpp_ptr() - lex->create_view_select.str; void *create_view_select= thd->memdup(lex->create_view_select.str, len); @@ -15843,7 +15980,6 @@ trigger_tail: EACH_SYM ROW_SYM { /* $15 */ - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; sp_head *sp; @@ -15877,8 +16013,8 @@ trigger_tail: sp_head *sp= lex->sphead; lex->sql_command= SQLCOM_CREATE_TRIGGER; - sp->set_stmt_end(YYTHD); - sp->restore_thd_mem_root(YYTHD); + sp->set_stmt_end(thd); + sp->restore_thd_mem_root(thd); if (sp->is_not_allowed_in_function("trigger")) MYSQL_YYABORT; @@ -15888,7 +16024,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(YYTHD, $9, + if (!lex->select_lex.add_table_to_list(thd, $9, (LEX_STRING*) 0, TL_OPTION_UPDATING, TL_READ_NO_INSERT, @@ -15907,7 +16043,6 @@ udf_tail: AGGREGATE_SYM remember_name FUNCTION_SYM ident RETURNS_SYM udf_type SONAME_SYM TEXT_STRING_sys { - THD *thd= YYTHD; LEX *lex= thd->lex; if (is_native_function(thd, & $4)) { @@ -15925,7 +16060,6 @@ udf_tail: | remember_name FUNCTION_SYM ident RETURNS_SYM udf_type SONAME_SYM TEXT_STRING_sys { - THD *thd= YYTHD; LEX *lex= thd->lex; if (is_native_function(thd, & $3)) { @@ -15948,7 +16082,6 @@ sf_tail: sp_name /* $3 */ '(' /* $4 */ { /* $5 */ - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; sp_head *sp; @@ -16007,7 +16140,7 @@ sf_tail: MYSQL_YYABORT; } - if (sp->fill_field_definition(YYTHD, lex, + if (sp->fill_field_definition(thd, lex, (enum enum_field_types) $11, &sp->m_return_field_def)) MYSQL_YYABORT; @@ -16016,7 +16149,6 @@ sf_tail: } sp_c_chistics /* $13 */ { /* $14 */ - THD *thd= YYTHD; LEX *lex= thd->lex; Lex_input_stream *lip= YYLIP; @@ -16025,7 +16157,6 @@ sf_tail: } sp_proc_stmt /* $15 */ { - THD *thd= YYTHD; LEX *lex= thd->lex; sp_head *sp= lex->sphead; @@ -16096,10 +16227,10 @@ sp_tail: sp= new sp_head(); if (sp == NULL) MYSQL_YYABORT; - sp->reset_thd_mem_root(YYTHD); + sp->reset_thd_mem_root(thd); sp->init(lex); sp->m_type= TYPE_ENUM_PROCEDURE; - sp->init_sp_name(YYTHD, $3); + sp->init_sp_name(thd, $3); lex->sphead= sp; } @@ -16114,7 +16245,6 @@ sp_tail: sp_pdparam_list ')' { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->sphead->m_param_end= YYLIP->get_cpp_tok_start(); @@ -16122,7 +16252,6 @@ sp_tail: } sp_c_chistics { - THD *thd= YYTHD; LEX *lex= thd->lex; lex->sphead->m_chistics= &lex->sp_chistics; @@ -16133,9 +16262,9 @@ sp_tail: LEX *lex= Lex; sp_head *sp= lex->sphead; - sp->set_stmt_end(YYTHD); + sp->set_stmt_end(thd); lex->sql_command= SQLCOM_CREATE_PROCEDURE; - sp->restore_thd_mem_root(YYTHD); + sp->restore_thd_mem_root(thd); } ; @@ -16172,21 +16301,21 @@ xid: text_string { MYSQL_YYABORT_UNLESS($1->length() <= MAXGTRIDSIZE); - if (!(Lex->xid=(XID *)YYTHD->alloc(sizeof(XID)))) + if (!(Lex->xid=(XID *)thd->alloc(sizeof(XID)))) MYSQL_YYABORT; Lex->xid->set(1L, $1->ptr(), $1->length(), 0, 0); } | text_string ',' text_string { MYSQL_YYABORT_UNLESS($1->length() <= MAXGTRIDSIZE && $3->length() <= MAXBQUALSIZE); - if (!(Lex->xid=(XID *)YYTHD->alloc(sizeof(XID)))) + if (!(Lex->xid=(XID *)thd->alloc(sizeof(XID)))) MYSQL_YYABORT; Lex->xid->set(1L, $1->ptr(), $1->length(), $3->ptr(), $3->length()); } | text_string ',' text_string ',' ulong_num { MYSQL_YYABORT_UNLESS($1->length() <= MAXGTRIDSIZE && $3->length() <= MAXBQUALSIZE); - if (!(Lex->xid=(XID *)YYTHD->alloc(sizeof(XID)))) + if (!(Lex->xid=(XID *)thd->alloc(sizeof(XID)))) MYSQL_YYABORT; Lex->xid->set($5, $1->ptr(), $1->length(), $3->ptr(), $3->length()); } diff --git a/sql/structs.h b/sql/structs.h index e5e65e01064..2de7abb666d 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -190,6 +190,14 @@ typedef int *(*update_var)(THD *, struct st_mysql_show_var *); typedef struct st_lex_user { LEX_STRING user, host, password, plugin, auth; + bool is_role() { 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; + } } LEX_USER; /* diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 18defddb36f..2407b57d26f 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -61,6 +61,7 @@ #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 @@ -1439,9 +1440,6 @@ Sys_var_gtid_binlog_pos::global_value_ptr(THD *thd, LEX_STRING *base) String str(buf, sizeof(buf), system_charset_info); char *p; - if (!rpl_global_gtid_slave_state.loaded) - return NULL; - str.length(0); if ((opt_bin_log && mysql_bin_log.append_state_pos(&str)) || !(p= thd->strmake(str.ptr(), str.length()))) @@ -1489,7 +1487,7 @@ Sys_var_gtid_slave_pos::do_check(THD *thd, set_var *var) DBUG_ASSERT(var->type == OPT_GLOBAL); - if (!rpl_global_gtid_slave_state.loaded) + 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); @@ -1554,11 +1552,17 @@ Sys_var_gtid_slave_pos::global_value_ptr(THD *thd, LEX_STRING *base) String str; char *p; - if (!rpl_global_gtid_slave_state.loaded) - return NULL; - str.length(0); - if (rpl_append_gtid_state(&str, false) || + /* + 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)); @@ -1584,9 +1588,185 @@ static Sys_var_mybool Sys_gtid_strict_mode( "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; + 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= (0 != reset_master(thd, data->list, data->list_len)); + 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, 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 bool +check_slave_parallel_threads(sys_var *self, THD *thd, set_var *var) +{ + bool running; + + mysql_mutex_lock(&LOCK_active_mi); + running= master_info_index->give_error_if_slave_running(); + mysql_mutex_unlock(&LOCK_active_mi); + if (running) + return true; + + return false; +} + +static bool +fix_slave_parallel_threads(sys_var *self, THD *thd, enum_var_type type) +{ + bool running; + bool err= false; + + mysql_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_lock(&LOCK_active_mi); + running= master_info_index->give_error_if_slave_running(); + mysql_mutex_unlock(&LOCK_active_mi); + if (running || rpl_parallel_change_thread_count(&global_rpl_thread_pool, + opt_slave_parallel_threads)) + err= true; + mysql_mutex_lock(&LOCK_global_system_variables); + + return err; +} + + +static Sys_var_ulong Sys_slave_parallel_threads( + "slave_parallel_threads", + "Alpha feature, to only be used by developers doing testing! " + "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.", + 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 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)); #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; @@ -2720,6 +2900,18 @@ static bool fix_tp_min_threads(sys_var *, THD *, enum_var_type) #ifndef _WIN32 +static bool check_threadpool_size(sys_var *self, THD *thd, set_var *var) +{ + ulonglong v= var->save_result.ulonglong_value; + if (v > threadpool_max_size) + { + var->save_result.ulonglong_value= threadpool_max_size; + return throw_bounds_warning(thd, self->name.str, true, true, v); + } + return false; +} + + static bool fix_threadpool_size(sys_var*, THD*, enum_var_type) { tp_set_threadpool_size(threadpool_size); @@ -2764,7 +2956,7 @@ static Sys_var_uint Sys_threadpool_size( "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), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_threadpool_size), ON_UPDATE(fix_threadpool_size) ); static Sys_var_uint Sys_threadpool_stall_limit( @@ -4424,11 +4616,12 @@ 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'", + "'innodb', 'query_plan', 'explain' ", SESSION_VAR(log_slow_verbosity), CMD_LINE(REQUIRED_ARG), log_slow_verbosity_names, DEFAULT(LOG_SLOW_VERBOSITY_INIT)); @@ -4573,6 +4766,8 @@ 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) diff --git a/sql/sys_vars.h b/sql/sys_vars.h index cd16dc8d80a..bef5fbdd126 100644 --- a/sql/sys_vars.h +++ b/sql/sys_vars.h @@ -1,4 +1,5 @@ -/* Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2002, 2011, Oracle and/or its affiliates. + Copyright (c) 2010, 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 @@ -140,6 +141,7 @@ public: option.u_max_value= (uchar**)max_var_ptr(); if (max_var_ptr()) *max_var_ptr()= max_val; + global_var(T)= def_val; SYSVAR_ASSERT(size == sizeof(T)); SYSVAR_ASSERT(min_val < max_val); @@ -431,7 +433,10 @@ public: void cleanup() { if (flags & ALLOCATED) + { my_free(global_var(char*)); + global_var(char *)= NULL; + } flags&= ~ALLOCATED; } static bool do_string_check(THD *thd, set_var *var, CHARSET_INFO *charset) @@ -993,13 +998,14 @@ public: on_update_function on_update_func=0, const char *substitute=0) : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id, - getopt.arg_type, SHOW_DOUBLE, (longlong) double2ulonglong(def_val), + getopt.arg_type, SHOW_DOUBLE, + (longlong) getopt_double2ulonglong(def_val), lock, binlog_status_arg, on_check_func, on_update_func, substitute) { option.var_type= GET_DOUBLE; - option.min_value= (longlong) double2ulonglong(min_val); - option.max_value= (longlong) double2ulonglong(max_val); + option.min_value= (longlong) getopt_double2ulonglong(min_val); + option.max_value= (longlong) getopt_double2ulonglong(max_val); global_var(double)= (double)option.def_value; SYSVAR_ASSERT(min_val < max_val); SYSVAR_ASSERT(min_val <= def_val); @@ -1031,7 +1037,7 @@ public: 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) - { var->save_result.double_value= (double)option.def_value; } + { var->save_result.double_value= getopt_ulonglong2double(option.def_value); } }; /** @@ -2191,3 +2197,44 @@ public: } uchar *global_value_ptr(THD *thd, LEX_STRING *base); }; + + +/** + 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); + bool check_update_type(Item_result type) { return type != STRING_RESULT; } + 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, LEX_STRING *base) + { + DBUG_ASSERT(false); + return NULL; + } + uchar *global_value_ptr(THD *thd, LEX_STRING *base); +}; diff --git a/sql/table.cc b/sql/table.cc index d68be3fad26..d0ad3e147f3 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -656,6 +656,220 @@ err_not_open: DBUG_RETURN(share->error); } +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; + KEY_PART_INFO *key_part= NULL; + ulong *rec_per_key= NULL; + KEY_PART_INFO *first_key_part= NULL; + uint first_key_parts= 0; + + if (!keys) + { + if (!(keyinfo = (KEY*) alloc_root(&share->mem_root, len))) + return 1; + bzero((char*) keyinfo, len); + key_part= reinterpret_cast<KEY_PART_INFO*> (keyinfo); + } + + /* + If share->use_ext_keys is set to TRUE we assume that any key + can be extended by the components of the primary key whose + definition is read first from the frm file. + For each key only those fields of the assumed primary key are + added that are not included in the proper key definition. + If after all it turns out that there is no primary key the + added components are removed from each key. + + When in the future we support others schemes of extending of + secondary keys with components of the primary key we'll have + to change the type of this flag for an enumeration type. + */ + + for (i=0 ; i < keys ; i++, 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->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->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->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))) + return 1; + bzero((char*) keyinfo,n_length); + share->key_info= keyinfo; + key_part= reinterpret_cast<KEY_PART_INFO*> (keyinfo + keys); + + if (!(rec_per_key= (ulong*) alloc_root(&share->mem_root, + sizeof(ulong) * ext_key_parts))) + return 1; + first_key_part= key_part; + first_key_parts= first_keyinfo->user_defined_key_parts; + keyinfo->flags= first_keyinfo->flags; + keyinfo->key_length= first_keyinfo->key_length; + 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; + } + + keyinfo->key_part= key_part; + keyinfo->rec_per_key= rec_per_key; + 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; + key_part->key_type= (uint) uint2korr(strpos+5); + // key_part->field= (Field*) 0; // Will be fixed later + if (new_frm_ver >= 1) + { + key_part->key_part_flag= *(strpos+4); + key_part->length= (uint) uint2korr(strpos+7); + strpos+=9; + } + else + { + key_part->length= *(strpos+4); + key_part->key_part_flag=0; + if (key_part->length > 128) + { + key_part->length&=127; /* purecov: inspected */ + key_part->key_part_flag=HA_REVERSE_SORT; /* purecov: inspected */ + } + strpos+=7; + } + key_part->store_length=key_part->length; + } + + /* + 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 && !(keyinfo->flags & HA_NOSAME)) + { + keyinfo->ext_key_part_map= 0; + for (j= 0; + j < first_key_parts && keyinfo->ext_key_parts < MAX_REF_PARTS; + j++) + { + 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++) + { + if (curr_key_part->fieldnr == first_key_part[j].fieldnr) + break; + } + if (curr_key_part == curr_key_part_end) + { + *key_part++= first_key_part[j]; + *rec_per_key++= 0; + keyinfo->ext_key_parts++; + keyinfo->ext_key_part_map|= 1 << j; + } + } + if (j == first_key_parts) + keyinfo->ext_key_flags= keyinfo->flags | HA_EXT_NOSAME; + } + share->ext_key_parts+= keyinfo->ext_key_parts; + } + keynames=(char*) key_part; + 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); + 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+= keyinfo->comment.length; + } + DBUG_ASSERT(test(keyinfo->flags & HA_USES_COMMENT) == + (keyinfo->comment.length > 0)); + } + + share->keys= keys; // do it *after* all key_info's are initialized + + return 0; +} + + +/** + 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 uint +upgrade_collation(ulong mysql_version, uint cs_number) +{ + 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 @@ -683,7 +897,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, uint db_create_options, keys, key_parts, n_length; uint com_length, null_bit_pos; uint extra_rec_buf_length; - uint i,j; + uint i; bool use_hash; char *keynames, *names, *comment_pos; const uchar *forminfo, *extra2; @@ -691,7 +905,6 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, uchar *record, *null_flags, *null_pos; const uchar *disk_buff, *strpos; ulong pos, record_offset; - ulong *rec_per_key= NULL; ulong rec_buff_length; handler *handler_file= 0; KEY *keyinfo; @@ -706,9 +919,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, const uchar *options= 0; KEY first_keyinfo; uint len; - KEY_PART_INFO *first_key_part= NULL; uint ext_key_parts= 0; - uint first_key_parts= 0; plugin_ref se_plugin= 0; keyinfo= &first_keyinfo; share->ext_key_parts= 0; @@ -837,12 +1048,18 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->null_field_first= 0; if (!frm_image[32]) // New frm file in 3.23 { + 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) (frm_image[39] & 3); share->page_checksum= (ha_choice) ((frm_image[39] >> 2) & 3); share->row_type= (enum row_type) frm_image[40]; - share->table_charset= get_charset((((uint) frm_image[41]) << 8) + - (uint) frm_image[38], MYF(0)); + + 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]); @@ -860,6 +1077,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } share->table_charset= default_charset_info; } + share->db_record_offset= 1; share->max_rows= uint4korr(frm_image+18); share->min_rows= uint4korr(frm_image+22); @@ -872,191 +1090,21 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (disk_buff[0] & 0x80) { - share->keys= keys= (disk_buff[1] << 7) | (disk_buff[0] & 0x7f); + keys= (disk_buff[1] << 7) | (disk_buff[0] & 0x7f); share->key_parts= key_parts= uint2korr(disk_buff+2); } else { - share->keys= keys= disk_buff[0]; + keys= disk_buff[0]; share->key_parts= key_parts= disk_buff[1]; } share->keys_for_keyread.init(0); share->keys_in_use.init(keys); - - /* - At this point we don't have enough information read from the frm file - to get a proper handlerton for the interesting engine in order to get - properties of this engine. - */ - /* Currently only InnoDB can use extended keys */ - share->set_use_ext_keys_flag(legacy_db_type == DB_TYPE_INNODB); + ext_key_parts= key_parts; len= (uint) uint2korr(disk_buff+4); - if (!keys) - { - if (!(keyinfo = (KEY*) alloc_root(&share->mem_root, len))) - goto err; - bzero((char*) keyinfo, len); - key_part= reinterpret_cast<KEY_PART_INFO*> (keyinfo+keys); - } - strpos= disk_buff+6; - - /* - If share->use_ext_keys is set to TRUE we assume that any key - can be extended by the components of the primary key whose - definition is read first from the frm file. - For each key only those fields of the assumed primary key are - added that are not included in the proper key definition. - If after all it turns out that there is no primary key the - added components are removed from each key. - - When in the future we support others schemes of extending of - secondary keys with components of the primary key we'll have - to change the type of this flag for an enumeration type. - */ - - for (i=0 ; i < keys ; i++, keyinfo++) - { - if (new_frm_ver >= 3) - { - if (strpos + 8 >= frm_image_end) - goto err; - keyinfo->flags= (uint) uint2korr(strpos) ^ HA_NOSAME; - keyinfo->key_length= (uint) uint2korr(strpos+2); - 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) - goto err; - keyinfo->flags= ((uint) strpos[0]) ^ HA_NOSAME; - keyinfo->key_length= (uint) uint2korr(strpos+1); - keyinfo->user_defined_key_parts= (uint) strpos[3]; - keyinfo->algorithm= HA_KEY_ALG_UNDEF; - strpos+=4; - } - if (i == 0) - { - ext_key_parts= 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))) - goto err; /* purecov: inspected */ - bzero((char*) keyinfo,n_length); - share->key_info= keyinfo; - key_part= reinterpret_cast<KEY_PART_INFO*> (keyinfo + keys); - - if (!(rec_per_key= (ulong*) alloc_root(&share->mem_root, - sizeof(ulong) * ext_key_parts))) - goto err; - first_key_part= key_part; - first_key_parts= first_keyinfo.user_defined_key_parts; - keyinfo->flags= first_keyinfo.flags; - keyinfo->key_length= first_keyinfo.key_length; - 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; - } - - keyinfo->key_part= key_part; - keyinfo->rec_per_key= rec_per_key; - for (j=keyinfo->user_defined_key_parts ; j-- ; key_part++) - { - if (strpos + (new_frm_ver >= 1 ? 9 : 7) >= frm_image_end) - goto err; - *rec_per_key++=0; - key_part->fieldnr= (uint16) (uint2korr(strpos) & FIELD_NR_MASK); - key_part->offset= (uint) uint2korr(strpos+2)-1; - key_part->key_type= (uint) uint2korr(strpos+5); - // key_part->field= (Field*) 0; // Will be fixed later - if (new_frm_ver >= 1) - { - key_part->key_part_flag= *(strpos+4); - key_part->length= (uint) uint2korr(strpos+7); - strpos+=9; - } - else - { - key_part->length= *(strpos+4); - key_part->key_part_flag=0; - if (key_part->length > 128) - { - key_part->length&=127; /* purecov: inspected */ - key_part->key_part_flag=HA_REVERSE_SORT; /* purecov: inspected */ - } - strpos+=7; - } - key_part->store_length=key_part->length; - } - - /* - 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 && !(keyinfo->flags & HA_NOSAME)) - { - keyinfo->ext_key_part_map= 0; - for (j= 0; - j < first_key_parts && keyinfo->ext_key_parts < MAX_REF_PARTS; - j++) - { - 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++) - { - if (curr_key_part->fieldnr == first_key_part[j].fieldnr) - break; - } - if (curr_key_part == curr_key_part_end) - { - *key_part++= first_key_part[j]; - *rec_per_key++= 0; - keyinfo->ext_key_parts++; - keyinfo->ext_key_part_map|= 1 << j; - } - } - if (j == first_key_parts) - keyinfo->ext_key_flags= keyinfo->flags | HA_EXT_NOSAME; - } - share->ext_key_parts+= keyinfo->ext_key_parts; - } - keynames=(char*) key_part; - strpos+= strnmov(keynames, (char *) strpos, frm_image_end - strpos) - keynames; - if (*strpos++) // key names are \0-terminated - goto err; - - //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) - goto err; - keyinfo->comment.length= uint2korr(strpos); - strpos+= 2; - - if (strpos + keyinfo->comment.length >= frm_image_end) - goto err; - keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos, - keyinfo->comment.length); - strpos+= keyinfo->comment.length; - } - DBUG_ASSERT(test(keyinfo->flags & HA_USES_COMMENT) == - (keyinfo->comment.length > 0)); - } - - share->reclength = uint2korr((frm_image+16)); + share->reclength = uint2korr(frm_image+16); share->stored_rec_length= share->reclength; if (frm_image[26] == 1) share->system= 1; /* one-record-database */ @@ -1101,7 +1149,10 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (se_plugin) { /* bad file, legacy_db_type did not match the name */ - goto err; + sql_print_warning("%s.frm is inconsistent: engine typecode %d, engine name %s (%d)", + share->normalized_path.str, legacy_db_type, + plugin_name(tmp_plugin)->str, + ha_legacy_type(plugin_data(tmp_plugin, handlerton *))); } /* tmp_plugin is locked with a local lock. @@ -1142,6 +1193,14 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } next_chunk+= str_db_type_length + 2; } + + share->set_use_ext_keys_flag(plugin_hton(se_plugin)->flags & HTON_EXTENDED_KEYS); + + 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; + if (next_chunk + 5 < buff_end) { uint32 partition_info_str_len = uint4korr(next_chunk); @@ -1228,6 +1287,14 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } DBUG_ASSERT(next_chunk <= buff_end); } + else + { + 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(frm_image+62); if (share->db_plugin && !plugin_equals(share->db_plugin, se_plugin)) @@ -1263,7 +1330,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, share->comment.length); } - DBUG_PRINT("info",("i_count: %d i_parts: %d index: %d n_length: %d int_length: %d com_length: %d vcol_screen_length: %d", interval_count,interval_parts, share->keys,n_length,int_length, com_length, vcol_screen_length)); + DBUG_PRINT("info",("i_count: %d i_parts: %d index: %d n_length: %d int_length: %d com_length: %d vcol_screen_length: %d", interval_count,interval_parts, keys,n_length,int_length, com_length, vcol_screen_length)); if (!(field_ptr = (Field **) @@ -1397,16 +1464,19 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } 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)))) { - const char *csname= get_charset_name((uint) csid); + const char *csname= get_charset_name((uint) cs_new); char tmp[10]; if (!csname || csname[0] =='?') { - my_snprintf(tmp, sizeof(tmp), "#%d", csid); + my_snprintf(tmp, sizeof(tmp), "#%d", cs_new); csname= tmp; } my_printf_error(ER_UNKNOWN_COLLATION, @@ -1617,12 +1687,12 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } else { - add_first_key_parts= first_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_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() != @@ -1635,7 +1705,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } } - for (uint key=0 ; key < share->keys ; key++,keyinfo++) + for (uint key=0 ; key < keys ; key++,keyinfo++) { uint usable_parts= 0; keyinfo->name=(char*) share->keynames.type_names[key]; @@ -1905,7 +1975,7 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, { reg_field= *share->found_next_number_field; if ((int) (share->next_number_index= (uint) - find_ref_key(share->key_info, share->keys, + find_ref_key(share->key_info, keys, share->default_values, reg_field, &share->next_number_key_offset, &share->next_number_keypart)) < 0) @@ -1976,7 +2046,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, } -static bool sql_unusable_for_discovery(THD *thd, const char *sql) +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; @@ -2008,7 +2079,7 @@ static bool sql_unusable_for_discovery(THD *thd, const char *sql) if (create_info->data_file_name || create_info->index_file_name) return 1; // ... engine - if (create_info->used_fields & HA_CREATE_USED_ENGINE) + if (create_info->db_type && create_info->db_type != engine) return 1; return 0; @@ -2028,6 +2099,7 @@ int TABLE_SHARE::init_from_sql_statement_string(THD *thd, bool write, LEX tmp_lex; KEY *unused1; uint unused2; + handlerton *hton= plugin_hton(db_plugin); LEX_CUSTRING frm= {0,0}; DBUG_ENTER("TABLE_SHARE::init_from_sql_statement_string"); @@ -2059,10 +2131,10 @@ int TABLE_SHARE::init_from_sql_statement_string(THD *thd, bool write, lex_start(thd); if ((error= parse_sql(thd, & parser_state, NULL) || - sql_unusable_for_discovery(thd, sql_copy))) + sql_unusable_for_discovery(thd, hton, sql_copy))) goto ret; - thd->lex->create_info.db_type= plugin_hton(db_plugin); + thd->lex->create_info.db_type= hton; if (tabledef_version.str) thd->lex->create_info.tabledef_version= tabledef_version; @@ -2194,7 +2266,7 @@ bool fix_vcol_expr(THD *thd, Item* func_expr= vcol_info->expr_item; bool result= TRUE; TABLE_LIST tables; - int error; + int error= 0; const char *save_where; Field **ptr, *field; enum_mark_columns save_mark_used_columns= thd->mark_used_columns; @@ -2207,7 +2279,11 @@ bool fix_vcol_expr(THD *thd, thd->where= "virtual column function"; /* Fix fields referenced to by the virtual column function */ - error= func_expr->fix_fields(thd, (Item**)0); + if (!func_expr->fixed) + error= func_expr->fix_fields(thd, &vcol_info->expr_item); + /* fix_fields could change the expression */ + func_expr= vcol_info->expr_item; + /* Number of columns will be checked later */ if (unlikely(error)) { @@ -2464,6 +2540,13 @@ enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share, outparam->db_stat= db_stat; outparam->write_row_record= NULL; + 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)) @@ -2987,6 +3070,11 @@ void open_table_error(TABLE_SHARE *share, enum open_frm_error 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 */ diff --git a/sql/table.h b/sql/table.h index 0f98f1dc0a3..301b98a5ec2 100644 --- a/sql/table.h +++ b/sql/table.h @@ -251,7 +251,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. @@ -559,7 +560,8 @@ enum open_frm_error { OPEN_FRM_DISCOVER, OPEN_FRM_ERROR_ALREADY_ISSUED, OPEN_FRM_NOT_A_VIEW, - OPEN_FRM_NOT_A_TABLE + OPEN_FRM_NOT_A_TABLE, + OPEN_FRM_NEEDS_REBUILD }; /** @@ -733,6 +735,13 @@ struct TABLE_SHARE 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; + + /* 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 @@ -865,7 +874,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 @@ -874,7 +883,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 @@ -889,8 +898,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 @@ -911,26 +920,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 @@ -940,7 +929,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, @@ -1007,6 +996,9 @@ struct st_cond_statistic; #define CHECK_ROW_FOR_NULLS_TO_REJECT (1 << 0) #define REJECT_ROW_DUE_TO_NULL_FIELDS (1 << 1) +/* Bitmap of table's fields */ +typedef Bitmap<MAX_FIELDS> Field_map; + struct TABLE { TABLE() {} /* Remove gcc warning */ @@ -1206,6 +1198,10 @@ public: */ bool distinct; bool const_table,no_rows, used_for_duplicate_elimination; + /** + Forces DYNAMIC Aria row format for internal temporary tables. + */ + bool keep_row_order; /** If set, the optimizer has found that row retrieval should access index @@ -1517,6 +1513,7 @@ typedef struct st_schema_table #define JOIN_TYPE_LEFT 1 #define JOIN_TYPE_RIGHT 2 +#define JOIN_TYPE_OUTER 4 /* Marker that this is an outer join */ #define VIEW_SUID_INVOKER 0 #define VIEW_SUID_DEFINER 1 @@ -2200,6 +2197,16 @@ struct TABLE_LIST bool single_table_updatable(); + bool is_inner_table_of_outer_join() + { + for (TABLE_LIST *tbl= this; tbl; tbl= tbl->embedding) + { + if (tbl->outer_join) + return true; + } + return false; + } + private: bool prep_check_option(THD *thd, uint8 check_opt_type); bool prep_where(THD *thd, Item **conds, bool no_where_clause); diff --git a/sql/threadpool.h b/sql/threadpool.h index 919836e5a57..c080e5ba343 100644 --- a/sql/threadpool.h +++ b/sql/threadpool.h @@ -13,12 +13,13 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define MAX_THREAD_GROUPS 128 +#define MAX_THREAD_GROUPS 100000 /* Threadpool parameters */ extern uint threadpool_min_threads; /* Minimum threads in pool */ extern uint threadpool_idle_timeout; /* Shutdown idle worker threads after this timeout */ extern uint threadpool_size; /* Number of parallel executing threads */ +extern uint threadpool_max_size; extern uint threadpool_stall_limit; /* time interval in 10 ms units for stall checks*/ extern uint threadpool_max_threads; /* Maximum threads in pool */ extern uint threadpool_oversubscribe; /* Maximum active threads in group */ diff --git a/sql/threadpool_common.cc b/sql/threadpool_common.cc index 5be06f0bdc8..9e0cb07b86c 100644 --- a/sql/threadpool_common.cc +++ b/sql/threadpool_common.cc @@ -30,6 +30,7 @@ uint threadpool_min_threads; uint threadpool_idle_timeout; uint threadpool_size; +uint threadpool_max_size; uint threadpool_stall_limit; uint threadpool_max_threads; uint threadpool_oversubscribe; diff --git a/sql/threadpool_unix.cc b/sql/threadpool_unix.cc index 0f88d4920b8..f0454cfedb0 100644 --- a/sql/threadpool_unix.cc +++ b/sql/threadpool_unix.cc @@ -147,8 +147,9 @@ struct thread_group_t } MY_ALIGNED(512); -static thread_group_t all_groups[MAX_THREAD_GROUPS]; +static thread_group_t *all_groups; static uint group_count; +static int32 shutdown_group_count; /** Used for printing "pool blocked" message, see @@ -517,7 +518,7 @@ static void* timer_thread(void *param) timer->current_microtime= microsecond_interval_timer(); /* Check stalls in thread groups */ - for(i=0; i< array_elements(all_groups);i++) + for (i= 0; i < threadpool_max_size; i++) { if(all_groups[i].connection_count) check_stall(&all_groups[i]); @@ -907,6 +908,7 @@ int thread_group_init(thread_group_t *thread_group, pthread_attr_t* thread_attr) thread_group->pollfd= -1; thread_group->shutdown_pipe[0]= -1; thread_group->shutdown_pipe[1]= -1; + thread_group->queue.empty(); DBUG_RETURN(0); } @@ -927,6 +929,8 @@ void thread_group_destroy(thread_group_t *thread_group) thread_group->shutdown_pipe[i]= -1; } } + if (my_atomic_add32(&shutdown_group_count, -1) == 1) + my_free(all_groups); } /** @@ -1510,10 +1514,18 @@ static void *worker_main(void *param) bool tp_init() { DBUG_ENTER("tp_init"); + 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) + { + threadpool_max_size= 0; + DBUG_RETURN(1); + } threadpool_started= true; scheduler_init(); - for(uint i=0; i < array_elements(all_groups); i++) + for (uint i= 0; i < threadpool_max_size; i++) { thread_group_init(&all_groups[i], get_connection_attrib()); } @@ -1542,7 +1554,8 @@ void tp_end() DBUG_VOID_RETURN; stop_timer(&pool_timer); - for(uint i=0; i< array_elements(all_groups); i++) + shutdown_group_count= threadpool_max_size; + for (uint i= 0; i < threadpool_max_size; i++) { thread_group_close(&all_groups[i]); } @@ -1604,9 +1617,7 @@ void tp_set_threadpool_stall_limit(uint limit) int tp_get_idle_thread_count() { int sum=0; - for(uint i= 0; - i< array_elements(all_groups) && (all_groups[i].pollfd >= 0); - i++) + for (uint i= 0; i < threadpool_max_size && all_groups[i].pollfd >= 0; i++) { sum+= (all_groups[i].thread_count - all_groups[i].active_thread_count); } diff --git a/sql/transaction.cc b/sql/transaction.cc index be1330d0a97..a214d1ed071 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -151,6 +151,11 @@ bool trans_begin(THD *thd, uint flags) } thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + + /* + The following set should not be needed as the flag should always be 0 + when we come here. We should at some point change this to an assert. + */ thd->transaction.all.modified_non_trans_table= FALSE; if (res) diff --git a/sql/wsrep_binlog.h b/sql/wsrep_binlog.h index 6de73b2f5ee..408cc9b425c 100644 --- a/sql/wsrep_binlog.h +++ b/sql/wsrep_binlog.h @@ -46,4 +46,8 @@ int wsrep_write_cache (wsrep_t* wsrep, /* Dump replication buffer to disk */ void wsrep_dump_rbr_buf(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_hton.cc b/sql/wsrep_hton.cc index 1a3b3d64cbe..e9d1a9289ff 100644 --- a/sql/wsrep_hton.cc +++ b/sql/wsrep_hton.cc @@ -22,8 +22,6 @@ #include <cstdio> #include <cstdlib> -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); @@ -77,8 +75,13 @@ void wsrep_register_hton(THD* thd, bool all) { trans_register_ha(thd, all, wsrep_hton); - /* follow innodb read/write settting */ - if (i->is_trx_read_write()) + /* 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(); } @@ -123,10 +126,7 @@ wsrep_close_connection(handlerton* hton, THD* thd) { 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); + DBUG_RETURN(wsrep_binlog_close_connection (thd)); } /* @@ -176,10 +176,11 @@ static int wsrep_savepoint_set(handlerton *hton, THD *thd, void *sv) DBUG_RETURN(0); } - if (!wsrep_emulate_bin_log) return 0; - int rcode = binlog_hton->savepoint_set(binlog_hton, thd, sv); - return rcode; + 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"); @@ -189,9 +190,9 @@ static int wsrep_savepoint_rollback(handlerton *hton, THD *thd, void *sv) DBUG_RETURN(0); } - if (!wsrep_emulate_bin_log) return 0; - int rcode = binlog_hton->savepoint_rollback(binlog_hton, thd, sv); - return rcode; + 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) @@ -204,6 +205,16 @@ static int wsrep_rollback(handlerton *hton, THD *thd, bool all) } mysql_mutex_lock(&thd->LOCK_wsrep_thd); + switch (thd->wsrep_exec_mode) + { + case TOTAL_ORDER: + case REPL_RECV: + mysql_mutex_unlock(&thd->LOCK_wsrep_thd); + 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)) { @@ -211,7 +222,7 @@ static int wsrep_rollback(handlerton *hton, THD *thd, bool all) { DBUG_PRINT("wsrep", ("setting rollback fail")); WSREP_ERROR("settting rollback fail: thd: %llu SQL: %s", - (long long)thd->real_id, thd->query()); + (long long)thd->real_id, thd->query()); } wsrep_cleanup_transaction(thd); } @@ -268,7 +279,7 @@ enum wsrep_trx_status wsrep_run_wsrep_commit(THD *thd, handlerton *hton, bool all) { int rcode= -1; - int data_len = 0; + size_t data_len= 0; IO_CACHE *cache; int replay_round= 0; @@ -324,6 +335,7 @@ wsrep_run_wsrep_commit(THD *thd, handlerton *hton, bool all) while (wsrep_replaying > 0 && thd->wsrep_conflict_state == NO_CONFLICT && + thd->killed == NOT_KILLED && !shutdown_in_progress) { @@ -377,7 +389,7 @@ wsrep_run_wsrep_commit(THD *thd, handlerton *hton, bool all) rcode = 0; if (cache) { thd->binlog_flush_pending_rows_event(true); - rcode = wsrep_write_cache(wsrep, thd, cache, (size_t*)&data_len); + 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_ROLLBACK); @@ -394,7 +406,7 @@ wsrep_run_wsrep_commit(THD *thd, handlerton *hton, bool all) "affected rows: %llu, " "changed tables: %d, " "sql_log_bin: %d, " - "wsrep status (%d %d %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, @@ -411,9 +423,9 @@ wsrep_run_wsrep_commit(THD *thd, handlerton *hton, bool all) if (WSREP_UNDEFINED_TRX_ID == thd->wsrep_ws_handle.trx_id) { WSREP_WARN("SQL statement was ineffective, THD: %lu, buf: %zu\n" - "QUERY: %s\n" - " => Skipping replication", - thd->thread_id, data_len, thd->query()); + "QUERY: %s\n" + " => Skipping replication", + thd->thread_id, data_len, thd->query()); rcode = WSREP_TRX_FAIL; } else if (!rcode) @@ -436,6 +448,7 @@ wsrep_run_wsrep_commit(THD *thd, handlerton *hton, bool all) thd->thread_id, (long long)thd->wsrep_trx_meta.gtid.seqno); mysql_mutex_lock(&thd->LOCK_wsrep_thd); thd->wsrep_conflict_state = MUST_REPLAY; + DBUG_ASSERT(wsrep_thd_trx_seqno(thd) > 0); mysql_mutex_unlock(&thd->LOCK_wsrep_thd); mysql_mutex_lock(&LOCK_wsrep_replaying); wsrep_replaying++; diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 137841c776f..6b9e3fe02ed 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1,4 +1,4 @@ -/* Copyright 2008 Codership Oy <http://www.codership.com> +/* Copyright 2008-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 @@ -27,9 +27,13 @@ #include <cstdlib> #include "log_event.h" -extern Format_description_log_event *wsrep_format_desc; wsrep_t *wsrep = NULL; 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 @@ -186,15 +190,36 @@ void wsrep_get_SE_checkpoint(XID* xid) plugin_foreach(NULL, get_SE_checkpoint, MYSQL_STORAGE_ENGINE_PLUGIN, xid); } +#ifdef GTID_SUPPORT +void wsrep_init_sidno(const wsrep_uuid_t& 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] = ~local_uuid.data[i]; + } + sid.copy_from(ltid_uuid.data); + global_sid_lock->wrlock(); + wsrep_sidno= global_sid_map->add_sid(sid); + WSREP_INFO("inited 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, +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) + 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 new_status= local_status.get(); if (memcmp(&cluster_uuid, &view->state_id.uuid, sizeof(wsrep_uuid_t))) @@ -268,16 +293,18 @@ wsrep_view_handler_cb (void* app_ctx, WSREP_DEBUG("[debug]: closing client connections for PRIM"); wsrep_close_client_connections(TRUE); - *sst_req_len= wsrep_sst_prepare (sst_req); + ssize_t const req_len= wsrep_sst_prepare (sst_req); - if (*sst_req_len < 0) + if (req_len < 0) { - int err = *sst_req_len; - WSREP_ERROR("SST preparation failed: %d (%s)", -err, strerror(-err)); + WSREP_ERROR("SST preparation failed: %zd (%s)", -req_len, + strerror(-req_len)); new_status= WSREP_MEMBER_UNDEFINED; } else { + assert(sst_req != NULL); + *sst_req_len= req_len; new_status= WSREP_MEMBER_JOINER; } } @@ -307,6 +334,9 @@ wsrep_view_handler_cb (void* app_ctx, wsrep_xid_init(&xid, &local_uuid, local_seqno); wsrep_set_SE_checkpoint(&xid); new_status= WSREP_MEMBER_JOINED; +#ifdef GTID_SUPPORT + wsrep_init_sidno(local_uuid); +#endif /* GTID_SUPPORT */ } // just some sanity check @@ -424,7 +454,7 @@ static void wsrep_init_position() } } -extern const char* my_bind_addr_str; +extern char* my_bind_addr_str; int wsrep_init() { @@ -432,7 +462,7 @@ int wsrep_init() wsrep_ready_set(FALSE); assert(wsrep_provider); - wsrep_format_desc= new Format_description_log_event(4); + wsrep_init_position(); if ((rcode= wsrep_load(wsrep_provider, &wsrep, wsrep_log_cb)) != WSREP_OK) @@ -637,9 +667,6 @@ void wsrep_deinit() provider_name[0]= '\0'; provider_version[0]= '\0'; provider_vendor[0]= '\0'; - - delete wsrep_format_desc; - wsrep_format_desc= NULL; } void wsrep_recover() @@ -741,12 +768,6 @@ bool wsrep_start_replication() return true; } - /* Note 'bootstrap' address is not officially supported in wsrep API #23 - but it can be back ported from #24 provider to get sneak preview of - bootstrap command - */ - const char* cluster_address = - wsrep_new_cluster ? "bootstrap" : wsrep_cluster_address; bool const bootstrap(TRUE == wsrep_new_cluster); wsrep_new_cluster= FALSE; @@ -754,8 +775,8 @@ bool wsrep_start_replication() if ((rcode = wsrep->connect(wsrep, wsrep_cluster_name, - cluster_address, - wsrep_sst_donor, + wsrep_cluster_address, + wsrep_sst_donor, bootstrap))) { if (-ESOCKTNOSUPPORT == rcode) @@ -920,11 +941,20 @@ static bool wsrep_prepare_keys_for_isolation(THD* thd, ka->keys_len= 0; extern TABLE* find_temporary_table(THD*, const TABLE_LIST*); - extern TABLE* find_temporary_table(THD*, const char *, const char *); if (db || table) { - if (!table || !find_temporary_table(thd, db, table)) + TABLE_LIST tmp_table; + MDL_request mdl_request; + + memset(&tmp_table, 0, sizeof(tmp_table)); + tmp_table.table_name= (char*)table; + tmp_table.db= (char*)db; + tmp_table.mdl_request.init(MDL_key::GLOBAL, (db) ? db : "", + (table) ? table : "", + MDL_INTENTION_EXCLUSIVE, MDL_STATEMENT); + + if (!table || !find_temporary_table(thd, &tmp_table)) { if (!(ka->keys= (wsrep_key_t*)my_malloc(sizeof(wsrep_key_t), MYF(0)))) { @@ -956,8 +986,9 @@ static bool wsrep_prepare_keys_for_isolation(THD* thd, { wsrep_key_t* tmp; tmp= (wsrep_key_t*)my_realloc( - ka->keys, (ka->keys_len + 1) * sizeof(wsrep_key_t), - MYF(MY_ALLOW_ZERO_PTR)); + ka->keys, (ka->keys_len + 1) * sizeof(wsrep_key_t), + MYF(MY_ALLOW_ZERO_PTR)); + if (!tmp) { WSREP_ERROR("Can't allocate memory for key_array"); @@ -989,9 +1020,8 @@ err: } - bool wsrep_prepare_key_for_innodb(const uchar* cache_key, - size_t cache_key_len, + size_t cache_key_len, const uchar* row_id, size_t row_id_len, wsrep_buf_t* key, @@ -1033,6 +1063,7 @@ bool wsrep_prepare_key_for_innodb(const uchar* cache_key, return true; } + /* * Construct Query_log_Event from thd query and serialize it * into buffer. @@ -1040,36 +1071,43 @@ bool wsrep_prepare_key_for_innodb(const uchar* cache_key, * 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, int* buf_len) + THD* thd, const char *query, uint query_len, uchar** buf, size_t* buf_len) { IO_CACHE tmp_io_cache; if (open_cached_file(&tmp_io_cache, mysql_tmpdir, TEMP_PREFIX, 65536, MYF(MY_WME))) return 1; - int ret(0); + +#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 && gtid_ev.write(&tmp_io_cache)) ret= 1; + } +#endif /* GTID_SUPPORT */ + /* if there is prepare query, add event for it */ - if (thd->wsrep_TOI_pre_query) + 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); + thd->wsrep_TOI_pre_query_len, + FALSE, FALSE, FALSE, 0); if (ev.write(&tmp_io_cache)) ret= 1; } - /* append the actual query */ + /* continue to append the actual query */ Query_log_event ev(thd, query, query_len, FALSE, FALSE, FALSE, 0); - if (ev.write(&tmp_io_cache)) ret= 1; - - if (!ret && wsrep_write_cache_buf(&tmp_io_cache, buf, (size_t*)buf_len)) ret= 1; - + if (!ret && ev.write(&tmp_io_cache)) ret= 1; + if (!ret && wsrep_write_cache_buf(&tmp_io_cache, buf, buf_len)) ret= 1; close_cached_file(&tmp_io_cache); return ret; } #include "sql_show.h" static int -create_view_query(THD *thd, uchar** buf, int* buf_len) +create_view_query(THD *thd, uchar** buf, size_t* buf_len) { LEX *lex= thd->lex; SELECT_LEX *select_lex= &lex->select_lex; @@ -1094,7 +1132,7 @@ create_view_query(THD *thd, uchar** buf, int* buf_len) the definer. */ - if (!(lex->definer= create_default_definer(thd))) + if (!(lex->definer= create_default_definer(thd, false))) { WSREP_WARN("view default definer issue"); } @@ -1134,7 +1172,7 @@ create_view_query(THD *thd, uchar** buf, int* buf_len) 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); + 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 @@ -1146,7 +1184,7 @@ static int wsrep_TOI_begin(THD *thd, char *db_, char *table_, { wsrep_status_t ret(WSREP_WARNING); uchar* buf(0); - int buf_len(0); + size_t buf_len(0); int buf_err; WSREP_DEBUG("TO BEGIN: %lld, %d : %s", (long long)wsrep_thd_trx_seqno(thd), @@ -1366,7 +1404,7 @@ void wsrep_to_isolation_end(THD *thd) msg, \ 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(), \ + 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()); @@ -1377,8 +1415,8 @@ wsrep_grant_mdl_exception(MDL_context *requestor_ctx, ) { if (!WSREP_ON) return FALSE; - THD *request_thd = requestor_ctx->get_thd(); - THD *granted_thd = ticket->get_ctx()->get_thd(); + THD *request_thd = requestor_ctx->wsrep_get_thd(); + THD *granted_thd = ticket->get_ctx()->wsrep_get_thd(); bool ret = FALSE; mysql_mutex_lock(&request_thd->LOCK_wsrep_thd); diff --git a/sql/wsrep_mysqld.h b/sql/wsrep_mysqld.h index 8580729c389..0704bc6503b 100644 --- a/sql/wsrep_mysqld.h +++ b/sql/wsrep_mysqld.h @@ -1,4 +1,4 @@ -/* Copyright 2008-2012 Codership Oy <http://www.codership.com> +/* Copyright 2008-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 @@ -19,6 +19,7 @@ #include "mysqld.h" typedef struct st_mysql_show_var SHOW_VAR; #include <sql_priv.h> +//#include "rpl_gtid.h" #include "../wsrep/wsrep_api.h" #define WSREP_UNDEFINED_TRX_ID ULONGLONG_MAX @@ -26,23 +27,22 @@ typedef struct st_mysql_show_var SHOW_VAR; class set_var; class THD; -#ifdef WITH_WSREP -#include "../wsrep/wsrep_api.h" - enum wsrep_exec_mode { +enum wsrep_exec_mode { LOCAL_STATE, REPL_RECV, TOTAL_ORDER, LOCAL_COMMIT - }; +}; - enum wsrep_query_state { +enum wsrep_query_state { QUERY_IDLE, QUERY_EXEC, QUERY_COMMITTING, QUERY_EXITING, QUERY_ROLLINGBACK, - }; - enum wsrep_conflict_state { +}; + +enum wsrep_conflict_state { NO_CONFLICT, MUST_ABORT, ABORTING, @@ -51,13 +51,14 @@ class THD; REPLAYING, RETRY_AUTOCOMMIT, CERT_FAILURE, - }; - enum wsrep_consistency_check_mode { +}; + +enum wsrep_consistency_check_mode { NO_CONSISTENCY_CHECK, CONSISTENCY_CHECK_DECLARED, CONSISTENCY_CHECK_RUNNING, - }; -#endif +}; + // Global wsrep parameters extern wsrep_t* wsrep; @@ -128,7 +129,6 @@ bool wsrep_before_SE(); // initialize wsrep before storage void wsrep_init_startup(bool before); - extern "C" enum wsrep_exec_mode wsrep_thd_exec_mode(THD *thd); extern "C" enum wsrep_conflict_state wsrep_thd_conflict_state(THD *thd); extern "C" enum wsrep_query_state wsrep_thd_query_state(THD *thd); @@ -145,8 +145,8 @@ extern "C" void wsrep_thd_set_conflict_state( extern "C" void wsrep_thd_set_trx_to_replay(THD *thd, uint64 trx_id); -extern "C"void wsrep_thd_LOCK(THD *thd); -extern "C"void wsrep_thd_UNLOCK(THD *thd); +extern "C" void wsrep_thd_LOCK(THD *thd); +extern "C" void wsrep_thd_UNLOCK(THD *thd); extern "C" uint32 wsrep_thd_wsrep_rand(THD *thd); extern "C" time_t wsrep_thd_query_start(THD *thd); extern "C" my_thread_id wsrep_thd_thread_id(THD *thd); @@ -155,8 +155,7 @@ extern "C" query_id_t wsrep_thd_query_id(THD *thd); extern "C" char * wsrep_thd_query(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 "C" void wsrep_thd_awake(THD* bf_thd, THD *thd, my_bool signal); - +extern "C" void wsrep_thd_awake(THD *thd, my_bool signal); extern void wsrep_close_client_connections(my_bool wait_to_end); @@ -230,7 +229,7 @@ enum wsrep_trx_status { WSREP_TRX_OK, WSREP_TRX_ROLLBACK, WSREP_TRX_ERROR, - }; +}; extern enum wsrep_trx_status wsrep_run_wsrep_commit(THD *thd, handlerton *hton, bool all); @@ -250,13 +249,13 @@ typedef struct wsrep_aborting_thd { } *wsrep_aborting_thd_t; extern mysql_mutex_t LOCK_wsrep_ready; -extern mysql_cond_t COND_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_cond_t COND_wsrep_sst; extern mysql_mutex_t LOCK_wsrep_sst_init; -extern mysql_cond_t COND_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 mysql_cond_t COND_wsrep_rollback; extern int wsrep_replaying; extern mysql_mutex_t LOCK_wsrep_replaying; extern mysql_cond_t COND_wsrep_replaying; @@ -265,6 +264,10 @@ extern mysql_mutex_t LOCK_wsrep_desync; 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 PSI_mutex_key key_LOCK_wsrep_ready; extern PSI_mutex_key key_COND_wsrep_ready; @@ -287,14 +290,15 @@ int wsrep_to_isolation_begin(THD *thd, char *db_, char *table_, 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, int* buf_len); -int wsrep_create_sp(THD *thd, uchar** buf, int* buf_len); -int wsrep_create_trigger_query(THD *thd, uchar** buf, int* buf_len); -int wsrep_create_event_query(THD *thd, uchar** buf, int* buf_len); + THD* thd, const char *query, uint query_len, uchar** buf, size_t* buf_len); +int wsrep_create_sp(THD *thd, uchar** buf, size_t* buf_len); +int wsrep_create_trigger_query(THD *thd, uchar** buf, size_t* buf_len); +int wsrep_create_event_query(THD *thd, uchar** buf, size_t* buf_len); struct xid_t; +void wsrep_get_SE_checkpoint(xid_t*); void wsrep_set_SE_checkpoint(xid_t*); - +void wsrep_init_sidno(const wsrep_uuid_t&); void wsrep_xid_init(xid_t*, const wsrep_uuid_t*, wsrep_seqno_t); const wsrep_uuid_t* wsrep_xid_uuid(const xid_t*); wsrep_seqno_t wsrep_xid_seqno(const xid_t*); diff --git a/sql/wsrep_sst.cc b/sql/wsrep_sst.cc index 6a97b29ff6d..bf34790a711 100644 --- a/sql/wsrep_sst.cc +++ b/sql/wsrep_sst.cc @@ -34,6 +34,7 @@ extern const char wsrep_defaults_file[]; #define WSREP_SST_OPT_DATA "--datadir" #define WSREP_SST_OPT_CONF "--defaults-file" #define WSREP_SST_OPT_PARENT "--parent" +#define WSREP_SST_OPT_BINLOG "--binlog" // mysqldump-specific options #define WSREP_SST_OPT_USER "--user" @@ -242,6 +243,9 @@ void wsrep_sst_received (wsrep_t* const wsrep, wsrep_gtid_t const state_id = { *uuid, (rcode ? WSREP_SEQNO_UNDEFINED : seqno) }; +#ifdef GTID_SUPPORT + wsrep_init_sidno(state_id.uuid); +#endif /* GTID_SUPPORT */ wsrep->sst_received(wsrep, &state_id, state, state_len, rcode); } @@ -402,6 +406,8 @@ static ssize_t sst_prepare_other (const char* method, ssize_t cmd_len= 1024; char cmd_str[cmd_len]; const char* sst_dir= mysql_real_data_home; + const char* binlog_opt= (opt_bin_logname ? (strcmp(opt_bin_logname, "0") ? WSREP_SST_OPT_BINLOG : "") : ""); + const char* binlog_opt_val= (opt_bin_logname ? (strcmp(opt_bin_logname, "0") ? opt_bin_logname : "") : ""); int ret= snprintf (cmd_str, cmd_len, "wsrep_sst_%s " @@ -410,9 +416,11 @@ static ssize_t sst_prepare_other (const char* method, WSREP_SST_OPT_AUTH" '%s' " WSREP_SST_OPT_DATA" '%s' " WSREP_SST_OPT_CONF" '%s' " - WSREP_SST_OPT_PARENT" '%d'", + WSREP_SST_OPT_PARENT" '%d'" + " %s '%s' ", method, addr_in, (sst_auth_real) ? sst_auth_real : "", - sst_dir, wsrep_defaults_file, (int)getpid()); + sst_dir, wsrep_defaults_file, (int)getpid(), + binlog_opt, binlog_opt_val); if (ret < 0 || ret >= cmd_len) { @@ -706,11 +714,9 @@ static int sst_donate_mysqldump (const char* addr, WSREP_SST_OPT_PORT" '%s' " WSREP_SST_OPT_LPORT" '%u' " WSREP_SST_OPT_SOCKET" '%s' " - WSREP_SST_OPT_DATA" '%s' " WSREP_SST_OPT_GTID" '%s:%lld'" "%s", - user, pswd, host, port, mysqld_port, mysqld_unix_port, - mysql_real_data_home, uuid_str, + user, pswd, host, port, mysqld_port, mysqld_unix_port, uuid_str, (long long)seqno, bypass ? " "WSREP_SST_OPT_BYPASS : ""); WSREP_DEBUG("Running: '%s'", cmd_str); @@ -766,8 +772,8 @@ static int sst_flush_tables(THD* thd) else { /* make sure logs are flushed after global read lock acquired */ - err= reload_acl_and_cache(thd, REFRESH_ENGINE_LOG, - (TABLE_LIST*) 0, ¬_used); + err= reload_acl_and_cache(thd, REFRESH_ENGINE_LOG | REFRESH_BINARY_LOG, + (TABLE_LIST*) 0, ¬_used); } if (err) @@ -927,6 +933,8 @@ static int sst_donate_other (const char* method, { ssize_t cmd_len = 4096; char cmd_str[cmd_len]; + const char* binlog_opt= (opt_bin_logname ? (strcmp(opt_bin_logname, "0") ? WSREP_SST_OPT_BINLOG : "") : ""); + const char* binlog_opt_val= (opt_bin_logname ? (strcmp(opt_bin_logname, "0") ? opt_bin_logname : "") : ""); int ret= snprintf (cmd_str, cmd_len, "wsrep_sst_%s " @@ -936,10 +944,12 @@ static int sst_donate_other (const char* method, WSREP_SST_OPT_SOCKET" '%s' " WSREP_SST_OPT_DATA" '%s' " WSREP_SST_OPT_CONF" '%s' " + " %s '%s' " WSREP_SST_OPT_GTID" '%s:%lld'" "%s", method, addr, sst_auth_real, mysqld_unix_port, mysql_real_data_home, wsrep_defaults_file, + binlog_opt, binlog_opt_val, uuid, (long long) seqno, bypass ? " "WSREP_SST_OPT_BYPASS : ""); @@ -959,7 +969,7 @@ static int sst_donate_other (const char* method, { WSREP_ERROR("sst_donate_other(): pthread_create() failed: %d (%s)", ret, strerror(ret)); - return -ret; + return ret; } mysql_cond_wait (&arg.cond, &arg.lock); @@ -967,11 +977,11 @@ static int sst_donate_other (const char* method, return arg.err; } -wsrep_cb_status 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) +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? */ @@ -995,6 +1005,7 @@ wsrep_cb_status wsrep_sst_donate_cb (void* app_ctx, void* recv_ctx, { ret = sst_donate_other(method, data, uuid_str, current_gtid->seqno,bypass); } + return (ret > 0 ? WSREP_CB_SUCCESS : WSREP_CB_FAILURE); } diff --git a/sql/wsrep_thd.cc b/sql/wsrep_thd.cc index b3338f753fd..2e1e86f2f30 100644 --- a/sql/wsrep_thd.cc +++ b/sql/wsrep_thd.cc @@ -19,10 +19,15 @@ #include "rpl_rli.h" #include "log_event.h" #include "sql_parse.h" -#include "slave.h" // opt_log_slave_updates +//#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" + static long long wsrep_bf_aborts_counter = 0; int wsrep_show_bf_aborts (THD *thd, SHOW_VAR *var, char *buff) @@ -73,8 +78,35 @@ void wsrep_client_rollback(THD *thd) thd->wsrep_conflict_state= ABORTED; } +#define NUMBER_OF_FIELDS_TO_IDENTIFY_COORDINATOR 1 +#define NUMBER_OF_FIELDS_TO_IDENTIFY_WORKER 2 +//#include "rpl_info_factory.h" + static Relay_log_info* wsrep_relay_log_init(const char* log_fname) { + + /* MySQL 5.6 version has rli factory: */ +#ifdef MYSQL_56 + uint rli_option = INFO_REPOSITORY_DUMMY; + Relay_log_info *rli= NULL; + rli = Rpl_info_factory::create_rli(rli_option, false); + rli->set_rli_description_event( + new Format_description_log_event(BINLOG_VERSION)); +#endif + Relay_log_info* rli= new Relay_log_info(false); + rli->sql_driver_thd= current_thd; + + rli->no_storage= true; + rli->relay_log.description_event_for_exec= + new Format_description_log_event(4); + + return rli; +} + +class Master_info; + +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; @@ -83,9 +115,20 @@ static Relay_log_info* wsrep_relay_log_init(const char* log_fname) rli->relay_log.description_event_for_exec= new Format_description_log_event(4); } + static LEX_STRING dbname= { C_STRING_WITH_LEN("mysql") }; - rli->sql_thd= current_thd; - return rli; + rli->mi = new Master_info( &dbname, false); + //rli->mi = new Master_info( &(C_STRING_WITH_LEN("wsrep")), false); + + rli->mi->rpl_filter = new Rpl_filter; + copy_filter_setting(rli->mi->rpl_filter, get_or_create_rpl_filter("", 0)); + + rli->sql_driver_thd= current_thd; + + struct rpl_group_info *rgi= new rpl_group_info(rli); + rgi->thd= current_thd; + + return rgi; } static void wsrep_prepare_bf_thd(THD *thd, struct wsrep_thd_shadow* shadow) @@ -100,7 +143,9 @@ static void wsrep_prepare_bf_thd(THD *thd, struct wsrep_thd_shadow* shadow) else thd->variables.option_bits&= ~(OPTION_BIN_LOG); - if (!thd->wsrep_rli) thd->wsrep_rli= wsrep_relay_log_init("wsrep_relay"); + //if (!thd->wsrep_rli) thd->wsrep_rli= wsrep_relay_log_init("wsrep_relay"); + if (!thd->wsrep_rgi) thd->wsrep_rgi= wsrep_relay_group_init("wsrep_relay"); + // thd->wsrep_rli->info_thd = thd; thd->wsrep_exec_mode= REPL_RECV; thd->net.vio= 0; @@ -123,12 +168,20 @@ static void wsrep_return_from_bf_mode(THD *thd, struct wsrep_thd_shadow* shadow) thd->net.vio = shadow->vio; thd->variables.tx_isolation = shadow->tx_isolation; thd->reset_db(shadow->db, shadow->db_length); + + delete thd->wsrep_rgi->rli->mi->rpl_filter; + delete thd->wsrep_rgi->rli->mi; + delete thd->wsrep_rgi->rli; + delete thd->wsrep_rgi; + thd->wsrep_rgi = NULL; +; } void wsrep_replay_transaction(THD *thd) { /* 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()) { @@ -139,7 +192,7 @@ void wsrep_replay_transaction(THD *thd) thd->wsrep_conflict_state= REPLAYING; mysql_mutex_unlock(&thd->LOCK_wsrep_thd); - mysql_reset_thd_for_next_command(thd, opt_userstat_running); + mysql_reset_thd_for_next_command(thd); thd->killed= NOT_KILLED; close_thread_tables(thd); if (thd->locked_tables_mode && thd->lock) @@ -150,7 +203,13 @@ void wsrep_replay_transaction(THD *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_proc_info(thd, "wsrep replaying trx"); WSREP_DEBUG("replay trx: %s %lld", thd->query() ? thd->query() : "void", @@ -204,7 +263,7 @@ void wsrep_replay_transaction(THD *thd) else { WSREP_DEBUG("replay failed, rolling back"); - my_error(ER_LOCK_DEADLOCK, MYF(0), "wsrep aborted transaction"); + //my_error(ER_LOCK_DEADLOCK, MYF(0), "wsrep aborted transaction"); } thd->wsrep_conflict_state= ABORTED; wsrep->post_rollback(wsrep, &thd->wsrep_ws_handle); @@ -286,9 +345,13 @@ static void wsrep_replication_process(THD *thd) mysql_cond_broadcast(&COND_thread_count); mysql_mutex_unlock(&LOCK_thread_count); - if (thd->temporary_tables) + TABLE *tmp; + while ((tmp = thd->temporary_tables)) { - WSREP_DEBUG("Applier %lu, has temporary tables at exit", thd->thread_id); + 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; @@ -374,6 +437,7 @@ static void wsrep_rollback_process(THD *thd) mysql_mutex_unlock(&aborting->LOCK_wsrep_thd); + set_current_thd(aborting); aborting->store_globals(); mysql_mutex_lock(&aborting->LOCK_wsrep_thd); @@ -382,6 +446,9 @@ static void wsrep_rollback_process(THD *thd) aborting->thread_id, (long long)aborting->real_id); mysql_mutex_unlock(&aborting->LOCK_wsrep_thd); + set_current_thd(thd); + thd->store_globals(); + mysql_mutex_lock(&LOCK_wsrep_rollback); } } diff --git a/sql/wsrep_utils.cc b/sql/wsrep_utils.cc index 90af2fb8156..fae9d97eedd 100644 --- a/sql/wsrep_utils.cc +++ b/sql/wsrep_utils.cc @@ -361,7 +361,7 @@ unsigned int wsrep_check_ip (const char* const addr) return ret; } -extern const char* my_bind_addr_str; +extern char* my_bind_addr_str; extern uint mysqld_port; size_t wsrep_guess_ip (char* buf, size_t buf_len) @@ -404,8 +404,10 @@ size_t wsrep_guess_ip (char* buf, size_t buf_len) // try to find the address of the first one #if (TARGET_OS_LINUX == 1) - const char cmd[] = "ip addr show | grep -E '^\\s*inet' | grep -m1 global |" - " awk '{ print $2 }' | sed 's/\\/.*//'"; + const char cmd[] = "/sbin/ifconfig | " +// "grep -m1 -1 -E '^[a-z]?eth[0-9]' | tail -n 1 | " + "grep -E '^[[:space:]]+inet addr:' | grep -m1 -v 'inet addr:127' | " + "sed 's/:/ /' | awk '{ print $3 }'"; #elif defined(__sun__) const char cmd[] = "/sbin/ifconfig -a | " "/usr/gnu/bin/grep -m1 -1 -E 'net[0-9]:' | tail -n 1 | awk '{ print $2 }'"; @@ -508,7 +510,7 @@ wsrep_seqno_t wsrep_xid_seqno(const XID* xid) } } -extern "C" +extern int wsrep_is_wsrep_xid(const void* xid_ptr) { const XID* xid= reinterpret_cast<const XID*>(xid_ptr); |