diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ace4084acd..a87d08cd1ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,7 +443,7 @@ ADD_CUSTOM_TARGET(INFO_BIN ALL WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) -INSTALL_DOCUMENTATION(README CREDITS COPYING COPYING.LESSER COPYING.thirdparty +INSTALL_DOCUMENTATION(README.md CREDITS COPYING COPYING.LESSER COPYING.thirdparty EXCEPTIONS-CLIENT COMPONENT Readme) # MDEV-6526 these files are not installed anymore #INSTALL_DOCUMENTATION(${CMAKE_BINARY_DIR}/Docs/INFO_SRC diff --git a/README b/README.md similarity index 88% rename from README rename to README.md index a9413f16b04..f34e6a43b71 100644 --- a/README +++ b/README.md @@ -1,3 +1,5 @@ +## MariaDB: drop-in replacement for MySQL + MariaDB is designed as a drop-in replacement of MySQL(R) with more features, new storage engines, fewer bugs, and better performance. @@ -24,10 +26,17 @@ https://mariadb.com/kb/en/mariadb-versus-mysql-compatibility/ As MariaDB is a full replacement of MySQL, the MySQL manual at http://dev.mysql.com/doc is generally applicable. +Help: +----- + More help is available from the Maria Discuss mailing list https://launchpad.net/~maria-discuss and the #maria IRC channel on Freenode. + +License: +-------- + *************************************************************************** NOTE: @@ -42,7 +51,8 @@ and COPYING.thirdparty files. *************************************************************************** -IMPORTANT: +Bug Reports: +------------ Bug and/or error reports regarding MariaDB should be submitted at http://mariadb.org/jira @@ -53,3 +63,8 @@ The code for MariaDB, including all revision history, can be found at: https://github.com/MariaDB/server *************************************************************************** + +Code status: +------------ + +* [![tests status](https://secure.travis-ci.org/MariaDB/server.png?branch=10.2)](https://travis-ci.org/MariaDB/server) travis-ci.org (10.2 branch) diff --git a/debian/mariadb-client-10.2.docs b/debian/mariadb-client-10.2.docs index 21446855f51..8117d689011 100644 --- a/debian/mariadb-client-10.2.docs +++ b/debian/mariadb-client-10.2.docs @@ -1,2 +1,2 @@ debian/additions/innotop/changelog.innotop -README +README.md diff --git a/include/mysql.h.pp b/include/mysql.h.pp index b9018376876..e1a0901cee8 100644 --- a/include/mysql.h.pp +++ b/include/mysql.h.pp @@ -87,6 +87,16 @@ enum enum_mysql_set_option MYSQL_OPTION_MULTI_STATEMENTS_ON, MYSQL_OPTION_MULTI_STATEMENTS_OFF }; +enum enum_session_state_type +{ + SESSION_TRACK_SYSTEM_VARIABLES, + SESSION_TRACK_SCHEMA, + SESSION_TRACK_STATE_CHANGE, + SESSION_TRACK_GTIDS, + SESSION_TRACK_TRANSACTION_CHARACTERISTICS, + SESSION_TRACK_TRANSACTION_STATE, + SESSION_TRACK_always_at_the_end +}; my_bool my_net_init(NET *net, Vio* vio, void *thd, unsigned int my_flags); void my_net_local_init(NET *net); void net_end(NET *net); diff --git a/include/mysql_com.h b/include/mysql_com.h index 8e7bf2337cc..82f3b9f62ba 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -72,6 +72,14 @@ #define INDEX_COMMENT_MAXLEN 1024 #define TABLE_PARTITION_COMMENT_MAXLEN 1024 +/* + Maximum length of protocol packet. + OK packet length limit also restricted to this value as any length greater + than this value will have first byte of OK packet to be 254 thus does not + provide a means to identify if this is OK or EOF packet. +*/ +#define MAX_PACKET_LENGTH (256L*256L*256L-1) + /* USER_HOST_BUFF_SIZE -- length of string buffer, that is enough to contain username and hostname parts of the user identifier with trailing zero in @@ -221,6 +229,14 @@ enum enum_server_command /* Don't close the connection for a connection with expired password. */ #define CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS (1UL << 22) +/** + Capable of handling server state change information. Its a hint to the + server to include the state change information in Ok packet. +*/ +#define CLIENT_SESSION_TRACK (1UL << 23) +/* Client no longer needs EOF packet */ +#define CLIENT_DEPRECATE_EOF (1UL << 24) + #define CLIENT_PROGRESS_OBSOLETE (1UL << 29) #define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) /* @@ -276,6 +292,8 @@ enum enum_server_command MARIADB_CLIENT_PROGRESS | \ CLIENT_PLUGIN_AUTH | \ CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | \ + CLIENT_SESSION_TRACK |\ + CLIENT_DEPRECATE_EOF |\ CLIENT_CONNECT_ATTRS |\ MARIADB_CLIENT_COM_MULTI) @@ -340,6 +358,11 @@ enum enum_server_command */ #define SERVER_STATUS_IN_TRANS_READONLY 8192 +/** + This status flag, when on, implies that one of the state information has + changed on the server because of the execution of the last statement. +*/ +#define SERVER_SESSION_STATE_CHANGED (1UL << 14) /** Server status flags that must be cleared when starting @@ -356,7 +379,8 @@ enum enum_server_command SERVER_QUERY_WAS_SLOW |\ SERVER_STATUS_DB_DROPPED |\ SERVER_STATUS_CURSOR_EXISTS|\ - SERVER_STATUS_LAST_ROW_SENT) + SERVER_STATUS_LAST_ROW_SENT|\ + SERVER_SESSION_STATE_CHANGED) #define MYSQL_ERRMSG_SIZE 512 #define NET_READ_TIMEOUT 30 /* Timeout on read */ @@ -523,6 +547,26 @@ enum enum_mysql_set_option MYSQL_OPTION_MULTI_STATEMENTS_OFF }; +/* + Type of state change information that the server can include in the Ok + packet. +*/ +enum enum_session_state_type +{ + SESSION_TRACK_SYSTEM_VARIABLES, /* Session system variables */ + SESSION_TRACK_SCHEMA, /* Current schema */ + SESSION_TRACK_STATE_CHANGE, /* track session state changes */ + SESSION_TRACK_GTIDS, + SESSION_TRACK_TRANSACTION_CHARACTERISTICS, /* Transaction chistics */ + SESSION_TRACK_TRANSACTION_STATE, /* Transaction state */ + SESSION_TRACK_always_at_the_end /* must be last */ +}; + +#define SESSION_TRACK_BEGIN SESSION_TRACK_SYSTEM_VARIABLES + +#define IS_SESSION_STATE_TYPE(T) \ + (((int)(T) >= SESSION_TRACK_BEGIN) && ((T) < SESSION_TRACK_always_at_the_end)) + #define net_new_transaction(net) ((net)->pkt_nr=0) #ifdef __cplusplus @@ -641,6 +685,7 @@ my_ulonglong net_field_length_ll(uchar **packet); my_ulonglong safe_net_field_length_ll(uchar **packet, size_t packet_len); uchar *net_store_length(uchar *pkg, ulonglong length); uchar *safe_net_store_length(uchar *pkg, size_t pkg_len, ulonglong length); +unsigned int net_length_size(ulonglong num); #endif #ifdef __cplusplus diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 368f30f8317..4518329a3dd 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -112,6 +112,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/item_windowfunc.cc ../sql/sql_window.cc ../sql/sql_cte.cc ../sql/temporary_tables.cc + ../sql/session_tracker.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} ) diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc index 4a84e63f222..097cde04b43 100644 --- a/libmysqld/lib_sql.cc +++ b/libmysqld/lib_sql.cc @@ -1172,7 +1172,7 @@ bool net_send_ok(THD *thd, uint server_status, uint statement_warn_count, ulonglong affected_rows, ulonglong id, const char *message, - bool unused __attribute__((unused))) + bool, bool) { DBUG_ENTER("emb_net_send_ok"); MYSQL_DATA *data; diff --git a/mysql-test/r/mysqld--help,win.rdiff b/mysql-test/r/mysqld--help,win.rdiff index aff42e2fa2d..5393af1dc99 100644 --- a/mysql-test/r/mysqld--help,win.rdiff +++ b/mysql-test/r/mysqld--help,win.rdiff @@ -23,9 +23,9 @@ + --shared-memory Enable the shared memory + --shared-memory-base-name=name + Base name of shared memory - --show-slave-auth-info - Show user and password in SHOW SLAVE HOSTS on this - master. + --session-track-schema + Track changes to the default schema. + (Defaults to on; use --skip-session-track-schema to disable.) @@ -1015,6 +1018,10 @@ Log slow queries to given log file. Defaults logging to 'hostname'-slow.log. Must be enabled to activate other @@ -103,12 +103,12 @@ @@ -1387,6 +1381,8 @@ secure-auth TRUE secure-file-priv (No default value) - server-id 0 + server-id 1 +shared-memory FALSE +shared-memory-base-name MYSQL - show-slave-auth-info FALSE - silent-startup FALSE - skip-grant-tables TRUE + session-track-schema TRUE + session-track-state-change FALSE + session-track-system-variables @@ -1411,6 +1407,7 @@ slave-type-conversions slow-launch-time 2 diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result index 0cb8fcaff1c..0d3a1f07225 100644 --- a/mysql-test/r/mysqld--help.result +++ b/mysql-test/r/mysqld--help.result @@ -906,6 +906,26 @@ The following options may be given as the first argument: files within specified directory --server-id=# Uniquely identifies the server instance in the community of replication partners + --session-track-schema + Track changes to the default schema. + (Defaults to on; use --skip-session-track-schema to disable.) + --session-track-state-change + Track changes to the session state. + --session-track-system-variables=name + Track changes in registered system variables. For + compatibility with MySQL defaults this variable should be + set to "autocommit, character_set_client, + character_set_connection, character_set_results, + time_zone" + --session-track-transaction-info=name + Track changes to the transaction attributes. OFF to + disable; STATE to track just transaction state (Is there + an active transaction? Does it have any data? etc.); + CHARACTERISTICS to track transaction state and report all + statements needed to start a transaction withthe same + characteristics (isolation level, read only/read + write,snapshot - but not any work done / data modified + within the transaction). --show-slave-auth-info Show user and password in SHOW SLAVE HOSTS on this master. @@ -1392,6 +1412,10 @@ safe-user-create FALSE secure-auth TRUE secure-file-priv (No default value) server-id 1 +session-track-schema TRUE +session-track-state-change FALSE +session-track-system-variables +session-track-transaction-info OFF show-slave-auth-info FALSE silent-startup FALSE skip-grant-tables TRUE diff --git a/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result b/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result new file mode 100644 index 00000000000..7162e40ef6b --- /dev/null +++ b/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result @@ -0,0 +1,164 @@ +# +# Variable name : session_track_system_variables +# Scope : Global & Session +# +# Global - default +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables + +# Session - default +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + + +# via INFORMATION_SCHEMA.GLOBAL_VARIABLES +SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track%' ORDER BY VARIABLE_NAME; +VARIABLE_NAME VARIABLE_VALUE +SESSION_TRACK_SCHEMA ON +SESSION_TRACK_STATE_CHANGE OFF +SESSION_TRACK_SYSTEM_VARIABLES +SESSION_TRACK_TRANSACTION_INFO OFF +# via INFORMATION_SCHEMA.SESSION_VARIABLES +SELECT * FROM INFORMATION_SCHEMA.SESSION_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track%' ORDER BY VARIABLE_NAME; +VARIABLE_NAME VARIABLE_VALUE +SESSION_TRACK_SCHEMA ON +SESSION_TRACK_STATE_CHANGE OFF +SESSION_TRACK_SYSTEM_VARIABLES +SESSION_TRACK_TRANSACTION_INFO OFF +SET @global_saved_tmp = @@global.session_track_system_variables; + +# Altering global variable's value +SET @@global.session_track_system_variables='autocommit'; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + + +# Altering session variable's value +SET @@session.session_track_system_variables='autocommit'; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit + +# Variables' values in a new session. +connect con1,"127.0.0.1",root,,test,$MASTER_MYPORT,; +# Global - expect "autocommit" +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit + +# Session - expect "autocommit" +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit + +# Switching to the default connection. +connection default; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +autocommit +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +autocommit + +# Test if DEFAULT is working as expected. +SET @@global.session_track_system_variables = DEFAULT; +SET @@session.session_track_system_variables = DEFAULT; + +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables + +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + + +# Variables' values in a new session (con2). +connect con2,"127.0.0.1",root,,test,$MASTER_MYPORT,; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables + +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + + +# Altering session should not affect global. +SET @@session.session_track_system_variables = 'sql_mode'; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables + +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +sql_mode + +# Variables' values in a new session (con3). +connect con3,"127.0.0.1",root,,test,$MASTER_MYPORT,; +# Altering global should not affect session. +SET @@global.session_track_system_variables = 'sql_mode'; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +sql_mode +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + + +# Switching to the default connection. +connection default; +# Testing NULL +SET @@global.session_track_system_variables = NULL; +SET @@session.session_track_system_variables = NULL; +# Global - expect "" instead of NULL +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +NULL +# Session - expect "" instead of NULL +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + +# testing with duplicate entries. +SET @@global.session_track_system_variables= "time_zone"; +SET @@session.session_track_system_variables= "time_zone"; +SET @@global.session_track_system_variables= "sql_mode,sql_mode"; +SET @@session.session_track_system_variables= "sql_mode,sql_mode"; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +sql_mode +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +sql_mode + +# testing ordering +SET @@global.session_track_system_variables= "time_zone,sql_mode"; +SET @@session.session_track_system_variables= "time_zone,sql_mode"; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +sql_mode,time_zone +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +sql_mode,time_zone + +# special values +SET @@global.session_track_system_variables= "*"; +SET @@session.session_track_system_variables= "*"; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables +* +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables +* +SET @@global.session_track_system_variables= ""; +SET @@session.session_track_system_variables= ""; +SELECT @@global.session_track_system_variables; +@@global.session_track_system_variables + +SELECT @@session.session_track_system_variables; +@@session.session_track_system_variables + + +# Restoring the original values. +SET @@global.session_track_system_variables = @global_saved_tmp; +# End of tests. diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 2dbb1db5bda..bf2e14700a7 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -3803,6 +3803,62 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED +VARIABLE_NAME SESSION_TRACK_SCHEMA +SESSION_VALUE ON +GLOBAL_VALUE ON +GLOBAL_VALUE_ORIGIN COMPILE-TIME +DEFAULT_VALUE ON +VARIABLE_SCOPE SESSION +VARIABLE_TYPE BOOLEAN +VARIABLE_COMMENT Track changes to the default schema. +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST OFF,ON +READ_ONLY NO +COMMAND_LINE_ARGUMENT OPTIONAL +VARIABLE_NAME SESSION_TRACK_STATE_CHANGE +SESSION_VALUE OFF +GLOBAL_VALUE OFF +GLOBAL_VALUE_ORIGIN COMPILE-TIME +DEFAULT_VALUE OFF +VARIABLE_SCOPE SESSION +VARIABLE_TYPE BOOLEAN +VARIABLE_COMMENT Track changes to the session state. +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST OFF,ON +READ_ONLY NO +COMMAND_LINE_ARGUMENT OPTIONAL +VARIABLE_NAME SESSION_TRACK_SYSTEM_VARIABLES +SESSION_VALUE +GLOBAL_VALUE +GLOBAL_VALUE_ORIGIN COMPILE-TIME +DEFAULT_VALUE +VARIABLE_SCOPE SESSION +VARIABLE_TYPE VARCHAR +VARIABLE_COMMENT Track changes in registered system variables. For compatibility with MySQL defaults this variable should be set to "autocommit, character_set_client, character_set_connection, character_set_results, time_zone" +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST NULL +READ_ONLY NO +COMMAND_LINE_ARGUMENT REQUIRED +VARIABLE_NAME SESSION_TRACK_TRANSACTION_INFO +SESSION_VALUE OFF +GLOBAL_VALUE OFF +GLOBAL_VALUE_ORIGIN COMPILE-TIME +DEFAULT_VALUE OFF +VARIABLE_SCOPE SESSION +VARIABLE_TYPE ENUM +VARIABLE_COMMENT Track changes to the transaction attributes. OFF to disable; STATE to track just transaction state (Is there an active transaction? Does it have any data? etc.); CHARACTERISTICS to track transaction state and report all statements needed to start a transaction withthe same characteristics (isolation level, read only/read write,snapshot - but not any work done / data modified within the transaction). +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST OFF,STATE,CHARACTERISTICS +READ_ONLY NO +COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME SKIP_EXTERNAL_LOCKING SESSION_VALUE NULL GLOBAL_VALUE ON diff --git a/mysql-test/suite/sys_vars/t/session_track_system_variables_basic.test b/mysql-test/suite/sys_vars/t/session_track_system_variables_basic.test new file mode 100644 index 00000000000..90e6052947c --- /dev/null +++ b/mysql-test/suite/sys_vars/t/session_track_system_variables_basic.test @@ -0,0 +1,129 @@ +--source include/not_embedded.inc + +--echo # +--echo # Variable name : session_track_system_variables +--echo # Scope : Global & Session +--echo # + +--echo # Global - default +SELECT @@global.session_track_system_variables; +--echo # Session - default +SELECT @@session.session_track_system_variables; +--echo + +--echo # via INFORMATION_SCHEMA.GLOBAL_VARIABLES +SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track%' ORDER BY VARIABLE_NAME; + +--echo # via INFORMATION_SCHEMA.SESSION_VARIABLES +SELECT * FROM INFORMATION_SCHEMA.SESSION_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track%' ORDER BY VARIABLE_NAME; + +# Save the global value to be used to restore the original value. +SET @global_saved_tmp = @@global.session_track_system_variables; +--echo + +--echo # Altering global variable's value +SET @@global.session_track_system_variables='autocommit'; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Altering session variable's value +SET @@session.session_track_system_variables='autocommit'; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Variables' values in a new session. +connect (con1,"127.0.0.1",root,,test,$MASTER_MYPORT,); + +--echo # Global - expect "autocommit" +SELECT @@global.session_track_system_variables; +--echo +--echo # Session - expect "autocommit" +SELECT @@session.session_track_system_variables; +--echo + +--echo # Switching to the default connection. +connection default; + +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Test if DEFAULT is working as expected. +SET @@global.session_track_system_variables = DEFAULT; +SET @@session.session_track_system_variables = DEFAULT; +--echo + +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Variables' values in a new session (con2). +connect (con2,"127.0.0.1",root,,test,$MASTER_MYPORT,); + +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Altering session should not affect global. +SET @@session.session_track_system_variables = 'sql_mode'; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Variables' values in a new session (con3). +connect (con3,"127.0.0.1",root,,test,$MASTER_MYPORT,); + +--echo # Altering global should not affect session. +SET @@global.session_track_system_variables = 'sql_mode'; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # Switching to the default connection. +connection default; + +--echo # Testing NULL +SET @@global.session_track_system_variables = NULL; +SET @@session.session_track_system_variables = NULL; + +--echo # Global - expect "" instead of NULL +SELECT @@global.session_track_system_variables; +--echo # Session - expect "" instead of NULL +SELECT @@session.session_track_system_variables; + +--echo # testing with duplicate entries. +# Lets first set it to some valid value. +SET @@global.session_track_system_variables= "time_zone"; +SET @@session.session_track_system_variables= "time_zone"; +# Now set with duplicate entries (must pass) +SET @@global.session_track_system_variables= "sql_mode,sql_mode"; +SET @@session.session_track_system_variables= "sql_mode,sql_mode"; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # testing ordering +SET @@global.session_track_system_variables= "time_zone,sql_mode"; +SET @@session.session_track_system_variables= "time_zone,sql_mode"; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + +--echo # special values +SET @@global.session_track_system_variables= "*"; +SET @@session.session_track_system_variables= "*"; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +SET @@global.session_track_system_variables= ""; +SET @@session.session_track_system_variables= ""; +SELECT @@global.session_track_system_variables; +SELECT @@session.session_track_system_variables; +--echo + + +--echo # Restoring the original values. +SET @@global.session_track_system_variables = @global_saved_tmp; + +--echo # End of tests. diff --git a/sql-common/pack.c b/sql-common/pack.c index 4bb4a0b7a4e..5428feb623e 100644 --- a/sql-common/pack.c +++ b/sql-common/pack.c @@ -186,3 +186,25 @@ uchar *safe_net_store_length(uchar *packet, size_t packet_len, ulonglong length) return packet+8; } + +/** + The length of space required to store the resulting length-encoded integer + for the given number. This function can be used at places where one needs to + dynamically allocate the buffer for a given number to be stored as length- + encoded integer. + + @param num [IN] the input number + + @return length of buffer needed to store this number [1, 3, 4, 9]. +*/ + +uint net_length_size(ulonglong num) +{ + if (num < (ulonglong) 251LL) + return 1; + if (num < (ulonglong) 65536LL) + return 3; + if (num < (ulonglong) 16777216LL) + return 4; + return 9; +} diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 089d793b2b0..a18294e5ae3 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -95,7 +95,9 @@ SET (SQL_SOURCE ../sql-common/client_plugin.c opt_range.cc opt_range.h opt_sum.cc ../sql-common/pack.c parse_file.cc password.c procedure.cc - protocol.cc records.cc repl_failsafe.cc rpl_filter.cc set_var.cc + protocol.cc records.cc repl_failsafe.cc rpl_filter.cc + session_tracker.cc + set_var.cc slave.cc sp.cc sp_cache.cc sp_head.cc sp_pcontext.cc sp_rcontext.cc spatial.cc sql_acl.cc sql_analyse.cc sql_base.cc sql_cache.cc sql_class.cc sql_client.cc sql_crypt.cc sql_crypt.h diff --git a/sql/lock.cc b/sql/lock.cc index 2e44786d6fe..07286324fc5 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -90,6 +90,7 @@ extern HASH open_cache; static int lock_external(THD *thd, TABLE **table,uint count); static int unlock_external(THD *thd, TABLE **table,uint count); + /* Map the return value of thr_lock to an error from errmsg.txt */ static int thr_lock_errno_to_mysql[]= { 0, ER_LOCK_ABORTED, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK }; @@ -243,6 +244,39 @@ void reset_lock_data(MYSQL_LOCK *sql_lock, bool unlock) } +/** + Scan array of tables for access types; update transaction tracker + accordingly. + + @param thd The current thread. + @param tables An array of pointers to the tables to lock. + @param count The number of tables to lock. +*/ + +#ifndef EMBEDDED_LIBRARY +static void track_table_access(THD *thd, TABLE **tables, size_t count) +{ + if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) + { + Transaction_state_tracker *tst= (Transaction_state_tracker *) + thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER); + + while (count--) + { + TABLE *t= tables[count]; + + if (t) + tst->add_trx_state(thd, t->reginfo.lock_type, + t->file->has_transactions()); + } + } +} +#else +#define track_table_access(A,B,C) +#endif //EMBEDDED_LIBRARY + + + /** Lock tables. @@ -280,6 +314,9 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags) my_free(sql_lock); sql_lock= 0; } + + track_table_access(thd, tables, count); + DBUG_RETURN(sql_lock); } diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 8fa8f01b894..28e91e208e7 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -690,6 +690,14 @@ THD *next_global_thread(THD *thd) } struct system_variables global_system_variables; +/** + Following is just for options parsing, used with a difference against + global_system_variables. + + TODO: something should be done to get rid of following variables +*/ +const char *current_dbug_option=""; + struct system_variables max_system_variables; struct system_status_var global_status_var; @@ -1463,7 +1471,6 @@ my_bool plugins_are_initialized= FALSE; #ifndef DBUG_OFF static const char* default_dbug_option; #endif -const char *current_dbug_option=""; #ifdef HAVE_LIBWRAP const char *libwrapName= NULL; int allow_severity = LOG_INFO; @@ -5278,6 +5285,17 @@ static int init_server_components() } plugins_are_initialized= TRUE; /* Don't separate from init function */ +#ifndef EMBEDDED_LIBRARY + { + if (Session_tracker::server_boot_verify(system_charset_info)) + { + sql_print_error("The variable session_track_system_variables has " + "invalid values."); + unireg_abort(1); + } + } +#endif //EMBEDDED_LIBRARY + /* we do want to exit if there are any other unknown options */ if (remaining_argc > 1) { diff --git a/sql/net_serv.cc b/sql/net_serv.cc index f0284462206..dc97d5e8e54 100644 --- a/sql/net_serv.cc +++ b/sql/net_serv.cc @@ -117,7 +117,6 @@ extern my_bool thd_net_is_killed(); #endif #define TEST_BLOCKING 8 -#define MAX_PACKET_LENGTH (256L*256L*256L-1) static my_bool net_write_buff(NET *, const uchar *, ulong); diff --git a/sql/protocol.cc b/sql/protocol.cc index 608ec553da0..9ad9269f3b5 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -35,7 +35,8 @@ static const unsigned int PACKET_BUFFER_EXTRA_ALLOC= 1024; /* Declared non-static only because of the embedded library. */ bool net_send_error_packet(THD *, uint, const char *, const char *); /* Declared non-static only because of the embedded library. */ -bool net_send_ok(THD *, uint, uint, ulonglong, ulonglong, const char *, bool); +bool net_send_ok(THD *, uint, uint, ulonglong, ulonglong, const char *, + bool, bool); /* Declared non-static only because of the embedded library. */ bool net_send_eof(THD *thd, uint server_status, uint statement_warn_count); #ifndef EMBEDDED_LIBRARY @@ -197,7 +198,8 @@ bool net_send_error(THD *thd, uint sql_errno, const char *err, @param affected_rows Number of rows changed by statement @param id Auto_increment id for first row (if used) @param message Message to send to the client (Used by mysql_status) - + @param is_eof this called instead of old EOF packet + @return @retval FALSE The message was successfully sent @retval TRUE An error occurred and the messages wasn't sent properly @@ -209,10 +211,14 @@ bool net_send_ok(THD *thd, uint server_status, uint statement_warn_count, ulonglong affected_rows, ulonglong id, const char *message, + bool is_eof, bool skip_flush) { NET *net= &thd->net; - uchar buff[MYSQL_ERRMSG_SIZE+10],*pos; + StringBuffer store; + + bool state_changed= false; + bool error= FALSE; DBUG_ENTER("net_send_ok"); @@ -222,38 +228,67 @@ net_send_ok(THD *thd, DBUG_RETURN(FALSE); } - buff[0]=0; // No fields - pos=net_store_length(buff+1,affected_rows); - pos=net_store_length(pos, id); + /* + OK send instead of EOF still require 0xFE header, but OK packet content. + */ + if (is_eof) + { + DBUG_ASSERT(thd->client_capabilities & CLIENT_DEPRECATE_EOF); + store.q_append((char)254); + } + else + store.q_append('\0'); + + /* affected rows */ + store.q_net_store_length(affected_rows); + + /* last insert id */ + store.q_net_store_length(id); + if (thd->client_capabilities & CLIENT_PROTOCOL_41) { DBUG_PRINT("info", ("affected_rows: %lu id: %lu status: %u warning_count: %u", - (ulong) affected_rows, + (ulong) affected_rows, (ulong) id, (uint) (server_status & 0xffff), (uint) statement_warn_count)); - int2store(pos, server_status); - pos+=2; + store.q_append2b(server_status); /* We can only return up to 65535 warnings in two bytes */ uint tmp= MY_MIN(statement_warn_count, 65535); - int2store(pos, tmp); - pos+= 2; + store.q_append2b(tmp); } else if (net->return_status) // For 4.0 protocol { - int2store(pos, server_status); - pos+=2; + store.q_append2b(server_status); } thd->get_stmt_da()->set_overwrite_status(true); - if (message && message[0]) - pos= net_store_data(pos, (uchar*) message, strlen(message)); - error= my_net_write(net, buff, (size_t) (pos-buff)); - if (!error && !skip_flush) + state_changed= + (thd->client_capabilities & CLIENT_SESSION_TRACK) && + (server_status & SERVER_SESSION_STATE_CHANGED); + + if (state_changed || (message && message[0])) + { + DBUG_ASSERT(safe_strlen(message) <= MYSQL_ERRMSG_SIZE); + store.q_net_store_data((uchar*) safe_str(message), safe_strlen(message)); + } + + if (unlikely(state_changed)) + { + store.set_charset(thd->variables.collation_database); + + thd->session_tracker.store(thd, &store); + } + + DBUG_ASSERT(store.length() <= MAX_PACKET_LENGTH); + + error= my_net_write(net, (const unsigned char*)store.ptr(), store.length()); + if (!error && (!skip_flush || is_eof)) error= net_flush(net); + thd->server_status&= ~SERVER_SESSION_STATE_CHANGED; thd->get_stmt_da()->set_overwrite_status(false); DBUG_PRINT("info", ("OK sent, so no more error sending allowed")); @@ -261,6 +296,7 @@ net_send_ok(THD *thd, DBUG_RETURN(error); } + static uchar eof_buff[1]= { (uchar) 254 }; /* Marker for end of fields */ /** @@ -292,6 +328,22 @@ net_send_eof(THD *thd, uint server_status, uint statement_warn_count) NET *net= &thd->net; bool error= FALSE; DBUG_ENTER("net_send_eof"); + + /* + Check if client understand new format packets (OK instead of EOF) + + Normally end of statement reply is signaled by OK packet, but in case + of binlog dump request an EOF packet is sent instead. Also, old clients + expect EOF packet instead of OK + */ + if ((thd->client_capabilities & CLIENT_DEPRECATE_EOF) && + (thd->get_command() != COM_BINLOG_DUMP )) + { + error= net_send_ok(thd, server_status, statement_warn_count, 0, 0, NULL, + true, false); + DBUG_RETURN(error); + } + /* Set to TRUE if no active vio, to work well in case of --init-file */ if (net->vio != 0) { @@ -546,9 +598,9 @@ bool Protocol::send_ok(uint server_status, uint statement_warn_count, const char *message, bool skip_flush) { DBUG_ENTER("Protocol::send_ok"); - const bool retval= + const bool retval= net_send_ok(thd, server_status, statement_warn_count, - affected_rows, last_insert_id, message, skip_flush); + affected_rows, last_insert_id, message, false, skip_flush); DBUG_RETURN(retval); } @@ -562,7 +614,7 @@ bool Protocol::send_ok(uint server_status, uint statement_warn_count, bool Protocol::send_eof(uint server_status, uint statement_warn_count) { DBUG_ENTER("Protocol::send_eof"); - const bool retval= net_send_eof(thd, server_status, statement_warn_count); + bool retval= net_send_eof(thd, server_status, statement_warn_count); DBUG_RETURN(retval); } @@ -862,14 +914,19 @@ bool Protocol::send_result_set_metadata(List *list, uint flags) if (flags & SEND_EOF) { - /* - Mark the end of meta-data result set, and store thd->server_status, - to show that there is no cursor. - Send no warning information, as it will be sent at statement end. - */ - if (write_eof_packet(thd, &thd->net, thd->server_status, - thd->get_stmt_da()->current_statement_warn_count())) - DBUG_RETURN(1); + + /* if it is new client do not send EOF packet */ + if (!(thd->client_capabilities & CLIENT_DEPRECATE_EOF)) + { + /* + Mark the end of meta-data result set, and store thd->server_status, + to show that there is no cursor. + Send no warning information, as it will be sent at statement end. + */ + if (write_eof_packet(thd, &thd->net, thd->server_status, + thd->get_stmt_da()->current_statement_warn_count())) + DBUG_RETURN(1); + } } DBUG_RETURN(prepare_for_send(list->elements)); @@ -1505,6 +1562,7 @@ bool Protocol_binary::store_time(MYSQL_TIME *tm, int decimals) bool Protocol_binary::send_out_parameters(List *sp_params) { + bool ret; if (!(thd->client_capabilities & CLIENT_PS_MULTI_RESULTS)) { /* The client does not support OUT-parameters. */ @@ -1558,8 +1616,7 @@ bool Protocol_binary::send_out_parameters(List *sp_params) /* Restore THD::server_status. */ thd->server_status&= ~SERVER_PS_OUT_PARAMS; - /* Send EOF-packet. */ - net_send_eof(thd, thd->server_status, 0); + ret= net_send_eof(thd, thd->server_status, 0); /* Reset SERVER_MORE_RESULTS_EXISTS bit, because this is the last packet @@ -1567,5 +1624,5 @@ bool Protocol_binary::send_out_parameters(List *sp_params) */ thd->server_status&= ~SERVER_MORE_RESULTS_EXISTS; - return FALSE; + return ret ? FALSE : TRUE; } diff --git a/sql/session_tracker.cc b/sql/session_tracker.cc new file mode 100644 index 00000000000..3272d2a41f0 --- /dev/null +++ b/sql/session_tracker.cc @@ -0,0 +1,1710 @@ +/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2016, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + + +#ifndef EMBEDDED_LIBRARY +#include "sql_plugin.h" +#include "session_tracker.h" + +#include "hash.h" +#include "table.h" +#include "rpl_gtid.h" +#include "sql_class.h" +#include "sql_show.h" +#include "sql_plugin.h" +#include "set_var.h" + +void State_tracker::mark_as_changed(THD *thd, LEX_CSTRING *tracked_item_name) +{ + m_changed= true; + thd->lex->safe_to_cache_query= 0; + thd->server_status|= SERVER_SESSION_STATE_CHANGED; +} + + +class Not_implemented_tracker : public State_tracker +{ +public: + bool enable(THD *thd) + { return false; } + bool update(THD *, set_var *) + { return false; } + bool store(THD *, String *) + { return false; } + void mark_as_changed(THD *, LEX_CSTRING *tracked_item_name) + {} + +}; + +/** + Session_sysvars_tracker + + This is a tracker class that enables & manages the tracking of session + system variables. It internally maintains a hash of user supplied variable + references and a boolean field to store if the variable was changed by the + last statement. +*/ + +class Session_sysvars_tracker : public State_tracker +{ +private: + + struct sysvar_node_st { + sys_var *m_svar; + bool *test_load; + bool m_changed; + }; + + class vars_list + { + private: + /** + Registered system variables. (@@session_track_system_variables) + A hash to store the name of all the system variables specified by the + user. + */ + HASH m_registered_sysvars; + /** Size of buffer for string representation */ + size_t buffer_length; + myf m_mem_flag; + /** + If TRUE then we want to check all session variable. + */ + bool track_all; + void init() + { + my_hash_init(&m_registered_sysvars, + &my_charset_bin, + 4, 0, 0, (my_hash_get_key) sysvars_get_key, + my_free, MYF(HASH_UNIQUE | + ((m_mem_flag & MY_THREAD_SPECIFIC) ? + HASH_THREAD_SPECIFIC : 0))); + } + void free_hash() + { + if (my_hash_inited(&m_registered_sysvars)) + { + my_hash_free(&m_registered_sysvars); + } + } + + uchar* search(const sys_var *svar) + { + return (my_hash_search(&m_registered_sysvars, (const uchar *)&svar, + sizeof(sys_var *))); + } + + public: + vars_list() : + buffer_length(0) + { + m_mem_flag= current_thd ? MY_THREAD_SPECIFIC : 0; + init(); + } + + size_t get_buffer_length() + { + DBUG_ASSERT(buffer_length != 0); // asked earlier then should + return buffer_length; + } + ~vars_list() + { + /* free the allocated hash. */ + if (my_hash_inited(&m_registered_sysvars)) + { + my_hash_free(&m_registered_sysvars); + } + } + + uchar* insert_or_search(sysvar_node_st *node, const sys_var *svar) + { + uchar *res; + res= search(svar); + if (!res) + { + if (track_all) + { + insert(node, svar, m_mem_flag); + return search(svar); + } + } + return res; + } + + bool insert(sysvar_node_st *node, const sys_var *svar, myf mem_flag); + void reinit(); + void reset(); + inline bool is_enabled() + { + return track_all || m_registered_sysvars.records; + } + void copy(vars_list* from, THD *thd); + bool parse_var_list(THD *thd, LEX_STRING var_list, bool throw_error, + CHARSET_INFO *char_set, bool take_mutex); + bool construct_var_list(char *buf, size_t buf_len); + bool store(THD *thd, String *buf); + }; + /** + Two objects of vars_list type are maintained to manage + various operations. + */ + vars_list *orig_list, *tool_list; + +public: + Session_sysvars_tracker() + { + orig_list= new (std::nothrow) vars_list(); + tool_list= new (std::nothrow) vars_list(); + } + + ~Session_sysvars_tracker() + { + if (orig_list) + delete orig_list; + if (tool_list) + delete tool_list; + } + + size_t get_buffer_length() + { + return orig_list->get_buffer_length(); + } + bool construct_var_list(char *buf, size_t buf_len) + { + return orig_list->construct_var_list(buf, buf_len); + } + + /** + Method used to check the validity of string provided + for session_track_system_variables during the server + startup. + */ + static bool server_init_check(THD *thd, CHARSET_INFO *char_set, + LEX_STRING var_list) + { + return check_var_list(thd, var_list, false, char_set, false); + } + + static bool server_init_process(THD *thd, CHARSET_INFO *char_set, + LEX_STRING var_list) + { + vars_list dummy; + bool result; + result= dummy.parse_var_list(thd, var_list, false, char_set, false); + if (!result) + dummy.construct_var_list(var_list.str, var_list.length + 1); + return result; + } + + void reset(); + bool enable(THD *thd); + bool check_str(THD *thd, LEX_STRING *val); + bool update(THD *thd, set_var *var); + bool store(THD *thd, String *buf); + void mark_as_changed(THD *thd, LEX_CSTRING *tracked_item_name); + /* callback */ + static uchar *sysvars_get_key(const char *entry, size_t *length, + my_bool not_used __attribute__((unused))); + + // hash iterators + static my_bool name_array_filler(void *ptr, void *data_ptr); + static my_bool store_variable(void *ptr, void *data_ptr); + static my_bool reset_variable(void *ptr, void *data_ptr); + + static bool check_var_list(THD *thd, LEX_STRING var_list, bool throw_error, + CHARSET_INFO *char_set, bool take_mutex); +}; + + + +/** + Current_schema_tracker, + + This is a tracker class that enables & manages the tracking of current + schema for a particular connection. +*/ + +class Current_schema_tracker : public State_tracker +{ +private: + bool schema_track_inited; + void reset(); + +public: + + Current_schema_tracker() + { + schema_track_inited= false; + } + + bool enable(THD *thd) + { return update(thd, NULL); } + bool update(THD *thd, set_var *var); + bool store(THD *thd, String *buf); +}; + +/* + Session_state_change_tracker + + This is a boolean tracker class that will monitor any change that contributes + to a session state change. + Attributes that contribute to session state change include: + - Successful change to System variables + - User defined variables assignments + - temporary tables created, altered or deleted + - prepared statements added or removed + - change in current database + - change of current role +*/ + +class Session_state_change_tracker : public State_tracker +{ +private: + + void reset(); + +public: + Session_state_change_tracker(); + bool enable(THD *thd) + { return update(thd, NULL); }; + bool update(THD *thd, set_var *var); + bool store(THD *thd, String *buf); + bool is_state_changed(THD*); +}; + + +/* To be used in expanding the buffer. */ +static const unsigned int EXTRA_ALLOC= 1024; + + +void Session_sysvars_tracker::vars_list::reinit() +{ + buffer_length= 0; + track_all= 0; + if (m_registered_sysvars.records) + my_hash_reset(&m_registered_sysvars); +} + +/** + Copy the given list. + + @param from Source vars_list object. + @param thd THD handle to retrive the charset in use. + + @retval true there is something to track + @retval false nothing to track +*/ + +void Session_sysvars_tracker::vars_list::copy(vars_list* from, THD *thd) +{ + reinit(); + track_all= from->track_all; + free_hash(); + buffer_length= from->buffer_length; + m_registered_sysvars= from->m_registered_sysvars; + from->init(); +} + +/** + Inserts the variable to be tracked into m_registered_sysvars hash. + + @param node Node to be inserted. + @param svar address of the system variable + + @retval false success + @retval true error +*/ + +bool Session_sysvars_tracker::vars_list::insert(sysvar_node_st *node, + const sys_var *svar, + myf mem_flag) +{ + if (!node) + { + if (!(node= (sysvar_node_st *) my_malloc(sizeof(sysvar_node_st), + MYF(MY_WME | mem_flag)))) + { + reinit(); + return true; + } + } + + node->m_svar= (sys_var *)svar; + node->test_load= node->m_svar->test_load; + node->m_changed= false; + if (my_hash_insert(&m_registered_sysvars, (uchar *) node)) + { + my_free(node); + if (!search((sys_var *)svar)) + { + //EOF (error is already reported) + reinit(); + return true; + } + } + return false; +} + +/** + Parse the specified system variables list. + + @Note In case of invalid entry a warning is raised per invalid entry. + This is done in order to handle 'potentially' valid system + variables from uninstalled plugins which might get installed in + future. + + + @param thd [IN] The thd handle. + @param var_list [IN] System variable list. + @param throw_error [IN] bool when set to true, returns an error + in case of invalid/duplicate values. + @param char_set [IN] charecter set information used for string + manipulations. + @param take_mutex [IN] take LOCK_plugin + + @return + true Error + false Success +*/ +bool Session_sysvars_tracker::vars_list::parse_var_list(THD *thd, + LEX_STRING var_list, + bool throw_error, + CHARSET_INFO *char_set, + bool take_mutex) +{ + const char separator= ','; + char *token, *lasts= NULL; + size_t rest= var_list.length; + reinit(); + + if (!var_list.str || var_list.length == 0) + { + buffer_length= 1; + return false; + } + + if(!strcmp(var_list.str,(const char *)"*")) + { + track_all= true; + buffer_length= 2; + return false; + } + + buffer_length= var_list.length + 1; + token= var_list.str; + + track_all= false; + /* + If Lock to the plugin mutex is not acquired here itself, it results + in having to acquire it multiple times in find_sys_var_ex for each + token value. Hence the mutex is handled here to avoid a performance + overhead. + */ + if (!thd || take_mutex) + mysql_mutex_lock(&LOCK_plugin); + for (;;) + { + sys_var *svar; + LEX_STRING var; + + lasts= (char *) memchr(token, separator, rest); + + var.str= token; + if (lasts) + { + var.length= (lasts - token); + rest-= var.length + 1; + } + else + var.length= rest; + + /* Remove leading/trailing whitespace. */ + trim_whitespace(char_set, &var); + + if(!strcmp(var.str,(const char *)"*")) + { + track_all= true; + } + else if ((svar= + find_sys_var_ex(thd, var.str, var.length, throw_error, true))) + { + if (insert(NULL, svar, m_mem_flag) == TRUE) + goto error; + } + else if (throw_error && thd) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_VALUE_FOR_VAR, + "%.*s is not a valid system variable and will" + "be ignored.", (int)var.length, token); + } + else + goto error; + + if (lasts) + token= lasts + 1; + else + break; + } + if (!thd || take_mutex) + mysql_mutex_unlock(&LOCK_plugin); + + return false; + +error: + if (!thd || take_mutex) + mysql_mutex_unlock(&LOCK_plugin); + return true; +} + + +bool Session_sysvars_tracker::check_var_list(THD *thd, + LEX_STRING var_list, + bool throw_error, + CHARSET_INFO *char_set, + bool take_mutex) +{ + const char separator= ','; + char *token, *lasts= NULL; + size_t rest= var_list.length; + + if (!var_list.str || var_list.length == 0 || + !strcmp(var_list.str,(const char *)"*")) + { + return false; + } + + token= var_list.str; + + /* + If Lock to the plugin mutex is not acquired here itself, it results + in having to acquire it multiple times in find_sys_var_ex for each + token value. Hence the mutex is handled here to avoid a performance + overhead. + */ + if (!thd || take_mutex) + mysql_mutex_lock(&LOCK_plugin); + for (;;) + { + LEX_STRING var; + + lasts= (char *) memchr(token, separator, rest); + + var.str= token; + if (lasts) + { + var.length= (lasts - token); + rest-= var.length + 1; + } + else + var.length= rest; + + /* Remove leading/trailing whitespace. */ + trim_whitespace(char_set, &var); + + if(!strcmp(var.str,(const char *)"*") && + !find_sys_var_ex(thd, var.str, var.length, throw_error, true)) + { + if (throw_error && take_mutex && thd) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_VALUE_FOR_VAR, + "%.*s is not a valid system variable and will" + "be ignored.", (int)var.length, token); + } + else + { + if (!thd || take_mutex) + mysql_mutex_unlock(&LOCK_plugin); + return true; + } + } + + if (lasts) + token= lasts + 1; + else + break; + } + if (!thd || take_mutex) + mysql_mutex_unlock(&LOCK_plugin); + + return false; +} + +struct name_array_filler_data +{ + LEX_CSTRING **names; + uint idx; + +}; + +/** Collects variable references into array */ +my_bool Session_sysvars_tracker::name_array_filler(void *ptr, + void *data_ptr) +{ + Session_sysvars_tracker::sysvar_node_st *node= + (Session_sysvars_tracker::sysvar_node_st *)ptr; + name_array_filler_data *data= (struct name_array_filler_data *)data_ptr; + if (*node->test_load) + data->names[data->idx++]= &node->m_svar->name; + return FALSE; +} + +/* Sorts variable references array */ +static int name_array_sorter(const void *a, const void *b) +{ + LEX_CSTRING **an= (LEX_CSTRING **)a, **bn=(LEX_CSTRING **)b; + size_t min= MY_MIN((*an)->length, (*bn)->length); + int res= strncmp((*an)->str, (*bn)->str, min); + if (res == 0) + res= ((int)(*bn)->length)- ((int)(*an)->length); + return res; +} + +/** + Construct variable list by internal hash with references +*/ + +bool Session_sysvars_tracker::vars_list::construct_var_list(char *buf, + size_t buf_len) +{ + struct name_array_filler_data data; + size_t left= buf_len; + size_t names_size= m_registered_sysvars.records * sizeof(LEX_CSTRING *); + const char separator= ','; + + if (unlikely(buf_len < 1)) + return true; + + if (unlikely(track_all)) + { + if (buf_len < 2) + return true; + buf[0]= '*'; + buf[1]= '\0'; + return false; + } + + if (m_registered_sysvars.records == 0) + { + buf[0]= '\0'; + return false; + } + + data.names= (LEX_CSTRING**)my_safe_alloca(names_size); + + if (unlikely(!data.names)) + return true; + + data.idx= 0; + + mysql_mutex_lock(&LOCK_plugin); + my_hash_iterate(&m_registered_sysvars, &name_array_filler, &data); + DBUG_ASSERT(data.idx <= m_registered_sysvars.records); + + /* + We check number of records again here because number of variables + could be reduced in case of plugin unload. + */ + if (m_registered_sysvars.records == 0) + { + mysql_mutex_unlock(&LOCK_plugin); + buf[0]= '\0'; + return false; + } + + my_qsort(data.names, data.idx, sizeof(LEX_CSTRING *), + &name_array_sorter); + + for(uint i= 0; i < data.idx; i++) + { + LEX_CSTRING *nm= data.names[i]; + size_t ln= nm->length + 1; + if (ln > left) + { + mysql_mutex_unlock(&LOCK_plugin); + my_safe_afree(data.names, names_size); + return true; + } + memcpy(buf, nm->str, nm->length); + buf[nm->length]= separator; + buf+= ln; + left-= ln; + } + mysql_mutex_unlock(&LOCK_plugin); + + buf--; buf[0]= '\0'; + my_safe_afree(data.names, names_size); + + return false; +} + +/** + Enable session tracker by parsing global value of tracked variables. + + @param thd [IN] The thd handle. + + @retval true Error + @retval false Success +*/ + +bool Session_sysvars_tracker::enable(THD *thd) +{ + mysql_mutex_lock(&LOCK_plugin); + LEX_STRING tmp; + tmp.str= global_system_variables.session_track_system_variables; + tmp.length= safe_strlen(tmp.str); + if (tool_list->parse_var_list(thd, tmp, + true, thd->charset(), false) == true) + { + mysql_mutex_unlock(&LOCK_plugin); + return true; + } + mysql_mutex_unlock(&LOCK_plugin); + orig_list->copy(tool_list, thd); + m_enabled= true; + + return false; +} + + +/** + Check system variable name(s). + + @note This function is called from the ON_CHECK() function of the + session_track_system_variables' sys_var class. + + @param thd [IN] The thd handle. + @param var [IN] A pointer to set_var holding the specified list of + system variable names. + + @retval true Error + @retval false Success +*/ + +inline bool Session_sysvars_tracker::check_str(THD *thd, LEX_STRING *val) +{ + return Session_sysvars_tracker::check_var_list(thd, *val, true, + thd->charset(), true); +} + + +/** + Once the value of the @@session_track_system_variables has been + successfully updated, this function calls + Session_sysvars_tracker::vars_list::copy updating the hash in orig_list + which represents the system variables to be tracked. + + @note This function is called from the ON_UPDATE() function of the + session_track_system_variables' sys_var class. + + @param thd [IN] The thd handle. + + @retval true Error + @retval false Success +*/ + +bool Session_sysvars_tracker::update(THD *thd, set_var *var) +{ + /* + We are doing via tool list because there possible errors with memory + in this case value will be unchanged. + */ + tool_list->reinit(); + if (tool_list->parse_var_list(thd, var->save_result.string_value, true, + thd->charset(), true)) + return true; + orig_list->copy(tool_list, thd); + return false; +} + + +/* + Function and structure to support storing variables from hash to the buffer. +*/ + +struct st_store_variable_param +{ + THD *thd; + String *buf; +}; + +my_bool Session_sysvars_tracker::store_variable(void *ptr, void *data_ptr) +{ + Session_sysvars_tracker::sysvar_node_st *node= + (Session_sysvars_tracker::sysvar_node_st *)ptr; + if (node->m_changed) + { + THD *thd= ((st_store_variable_param *)data_ptr)->thd; + String *buf= ((st_store_variable_param *)data_ptr)->buf; + char val_buf[SHOW_VAR_FUNC_BUFF_SIZE]; + SHOW_VAR show; + CHARSET_INFO *charset; + size_t val_length, length; + mysql_mutex_lock(&LOCK_plugin); + if (!*node->test_load) + { + mysql_mutex_unlock(&LOCK_plugin); + return false; + } + sys_var *svar= node->m_svar; + bool is_plugin= svar->cast_pluginvar(); + if (!is_plugin) + mysql_mutex_unlock(&LOCK_plugin); + + /* As its always system variable. */ + show.type= SHOW_SYS; + show.name= svar->name.str; + show.value= (char *) svar; + + const char *value= get_one_variable(thd, &show, OPT_SESSION, SHOW_SYS, NULL, + &charset, val_buf, &val_length); + if (is_plugin) + mysql_mutex_unlock(&LOCK_plugin); + + length= net_length_size(svar->name.length) + + svar->name.length + + net_length_size(val_length) + + val_length; + + compile_time_assert(SESSION_TRACK_SYSTEM_VARIABLES < 251); + if (unlikely((1 + net_length_size(length) + length + buf->length() >= + MAX_PACKET_LENGTH) || + buf->reserve(1 + net_length_size(length) + length, + EXTRA_ALLOC))) + return true; + + + /* Session state type (SESSION_TRACK_SYSTEM_VARIABLES) */ + buf->q_append((char)SESSION_TRACK_SYSTEM_VARIABLES); + + /* Length of the overall entity. */ + buf->q_net_store_length((ulonglong)length); + + /* System variable's name (length-encoded string). */ + buf->q_net_store_data((const uchar*)svar->name.str, svar->name.length); + + /* System variable's value (length-encoded string). */ + buf->q_net_store_data((const uchar*)value, val_length); + } + return false; +} + +bool Session_sysvars_tracker::vars_list::store(THD *thd, String *buf) +{ + st_store_variable_param data= {thd, buf}; + return my_hash_iterate(&m_registered_sysvars, &store_variable, &data); +} + +/** + Store the data for changed system variables in the specified buffer. + Once the data is stored, we reset the flags related to state-change + (see reset()). + + @param thd [IN] The thd handle. + @paran buf [INOUT] Buffer to store the information to. + + @retval true Error + @retval false Success +*/ + +bool Session_sysvars_tracker::store(THD *thd, String *buf) +{ + if (!orig_list->is_enabled()) + return false; + + if (orig_list->store(thd, buf)) + return true; + + reset(); + + return false; +} + + +/** + Mark the system variable as changed. + + @param [IN] pointer on a variable +*/ + +void Session_sysvars_tracker::mark_as_changed(THD *thd, + LEX_CSTRING *var) +{ + sysvar_node_st *node= NULL; + sys_var *svar= (sys_var *)var; + /* + Check if the specified system variable is being tracked, if so + mark it as changed and also set the class's m_changed flag. + */ + if (orig_list->is_enabled() && + (node= (sysvar_node_st *) (orig_list->insert_or_search(node, svar)))) + { + node->m_changed= true; + State_tracker::mark_as_changed(thd, var); + } +} + + +/** + Supply key to a hash. + + @param entry [IN] A single entry. + @param length [OUT] Length of the key. + @param not_used Unused. + + @return Pointer to the key buffer. +*/ + +uchar *Session_sysvars_tracker::sysvars_get_key(const char *entry, + size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length= sizeof(sys_var *); + return (uchar *) &(((sysvar_node_st *) entry)->m_svar); +} + + +/* Function to support resetting hash nodes for the variables */ + +my_bool Session_sysvars_tracker::reset_variable(void *ptr, + void *data_ptr) +{ + ((Session_sysvars_tracker::sysvar_node_st *)ptr)->m_changed= false; + return false; +} + +void Session_sysvars_tracker::vars_list::reset() +{ + my_hash_iterate(&m_registered_sysvars, &reset_variable, NULL); +} + +/** + Prepare/reset the m_registered_sysvars hash for next statement. +*/ + +void Session_sysvars_tracker::reset() +{ + + orig_list->reset(); + m_changed= false; +} + +static Session_sysvars_tracker* sysvar_tracker(THD *thd) +{ + return (Session_sysvars_tracker*) + thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER); +} + +bool sysvartrack_validate_value(THD *thd, const char *str, size_t len) +{ + LEX_STRING tmp= {(char *)str, len}; + return Session_sysvars_tracker::server_init_check(thd, system_charset_info, + tmp); +} +bool sysvartrack_reprint_value(THD *thd, char *str, size_t len) +{ + LEX_STRING tmp= {str, len}; + return Session_sysvars_tracker::server_init_process(thd, + system_charset_info, + tmp); +} +bool sysvartrack_update(THD *thd, set_var *var) +{ + return sysvar_tracker(thd)->update(thd, var); +} +size_t sysvartrack_value_len(THD *thd) +{ + return sysvar_tracker(thd)->get_buffer_length(); +} +bool sysvartrack_value_construct(THD *thd, char *val, size_t len) +{ + return sysvar_tracker(thd)->construct_var_list(val, len); +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + Enable/disable the tracker based on @@session_track_schema's value. + + @param thd [IN] The thd handle. + + @return + false (always) +*/ + +bool Current_schema_tracker::update(THD *thd, set_var *) +{ + m_enabled= thd->variables.session_track_schema; + return false; +} + + +/** + Store the schema name as length-encoded string in the specified buffer. + + @param thd [IN] The thd handle. + @paran buf [INOUT] Buffer to store the information to. + + @reval false Success + @retval true Error +*/ + +bool Current_schema_tracker::store(THD *thd, String *buf) +{ + ulonglong db_length, length; + + /* + Protocol made (by unknown reasons) redundant: + It saves length of database name and name of database name + + length of saved length of database length. + */ + length= db_length= thd->db_length; + length += net_length_size(length); + + compile_time_assert(SESSION_TRACK_SCHEMA < 251); + compile_time_assert(NAME_LEN < 251); + DBUG_ASSERT(length < 251); + if (unlikely((1 + 1 + length + buf->length() >= MAX_PACKET_LENGTH) || + buf->reserve(1 + 1 + length, EXTRA_ALLOC))) + return true; + + /* Session state type (SESSION_TRACK_SCHEMA) */ + buf->q_append((char)SESSION_TRACK_SCHEMA); + + /* Length of the overall entity. */ + buf->q_net_store_length(length); + + /* Length and current schema name */ + buf->q_net_store_data((const uchar *)thd->db, thd->db_length); + + reset(); + + return false; +} + + +/** + Reset the m_changed flag for next statement. + + @return void +*/ + +void Current_schema_tracker::reset() +{ + m_changed= false; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +Transaction_state_tracker::Transaction_state_tracker() +{ + m_enabled = false; + tx_changed = TX_CHG_NONE; + tx_curr_state = + tx_reported_state= TX_EMPTY; + tx_read_flags = TX_READ_INHERIT; + tx_isol_level = TX_ISOL_INHERIT; +} + +/** + Enable/disable the tracker based on @@session_track_transaction_info. + + @param thd [IN] The thd handle. + + @retval true if updating the tracking level failed + @retval false otherwise +*/ + +bool Transaction_state_tracker::update(THD *thd, set_var *) +{ + if (thd->variables.session_track_transaction_info != TX_TRACK_NONE) + { + /* + If we only just turned reporting on (rather than changing between + state and characteristics reporting), start from a defined state. + */ + if (!m_enabled) + { + tx_curr_state = + tx_reported_state = TX_EMPTY; + tx_changed |= TX_CHG_STATE; + m_enabled= true; + } + if (thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS) + tx_changed |= TX_CHG_CHISTICS; + mark_as_changed(thd, NULL); + } + else + m_enabled= false; + + return false; +} + + +/** + Store the transaction state (and, optionally, characteristics) + as length-encoded string in the specified buffer. Once the data + is stored, we reset the flags related to state-change (see reset()). + + + @param thd [IN] The thd handle. + @paran buf [INOUT] Buffer to store the information to. + + @retval false Success + @retval true Error +*/ + +static LEX_CSTRING isol[]= { + { STRING_WITH_LEN("READ UNCOMMITTED") }, + { STRING_WITH_LEN("READ COMMITTED") }, + { STRING_WITH_LEN("REPEATABLE READ") }, + { STRING_WITH_LEN("SERIALIZABLE") } +}; + +bool Transaction_state_tracker::store(THD *thd, String *buf) +{ + /* STATE */ + if (tx_changed & TX_CHG_STATE) + { + if (unlikely((11 + buf->length() >= MAX_PACKET_LENGTH) || + buf->reserve(11, EXTRA_ALLOC))) + return true; + + buf->q_append((char)SESSION_TRACK_TRANSACTION_STATE); + + buf->q_append((char)9); // whole packet length + buf->q_append((char)8); // results length + + buf->q_append((char)((tx_curr_state & TX_EXPLICIT) ? 'T' : + ((tx_curr_state & TX_IMPLICIT) ? 'I' : '_'))); + buf->q_append((char)((tx_curr_state & TX_READ_UNSAFE) ? 'r' : '_')); + buf->q_append((char)(((tx_curr_state & TX_READ_TRX) || + (tx_curr_state & TX_WITH_SNAPSHOT)) ? 'R' : '_')); + buf->q_append((char)((tx_curr_state & TX_WRITE_UNSAFE) ? 'w' : '_')); + buf->q_append((char)((tx_curr_state & TX_WRITE_TRX) ? 'W' : '_')); + buf->q_append((char)((tx_curr_state & TX_STMT_UNSAFE) ? 's' : '_')); + buf->q_append((char)((tx_curr_state & TX_RESULT_SET) ? 'S' : '_')); + buf->q_append((char)((tx_curr_state & TX_LOCKED_TABLES) ? 'L' : '_')); + } + + /* CHARACTERISTICS -- How to restart the transaction */ + + if ((thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS) && + (tx_changed & TX_CHG_CHISTICS)) + { + bool is_xa= (thd->transaction.xid_state.xa_state != XA_NOTR); + size_t start; + + /* 2 length by 1 byte and code */ + if (unlikely((1 + 1 + 1 + 110 + buf->length() >= MAX_PACKET_LENGTH) || + buf->reserve(1 + 1 + 1, EXTRA_ALLOC))) + return true; + + compile_time_assert(SESSION_TRACK_TRANSACTION_CHARACTERISTICS < 251); + /* Session state type (SESSION_TRACK_TRANSACTION_CHARACTERISTICS) */ + buf->q_append((char)SESSION_TRACK_TRANSACTION_CHARACTERISTICS); + + /* placeholders for lengths. will be filled in at the end */ + buf->q_append('\0'); + buf->q_append('\0'); + + start= buf->length(); + + { + /* + We have four basic replay scenarios: + + a) SET TRANSACTION was used, but before an actual transaction + was started, the load balancer moves the connection elsewhere. + In that case, the same one-shots should be set up in the + target session. (read-only/read-write; isolation-level) + + b) The initial transaction has begun; the relevant characteristics + are the session defaults, possibly overridden by previous + SET TRANSACTION statements, possibly overridden or extended + by options passed to the START TRANSACTION statement. + If the load balancer wishes to move this transaction, + it needs to be replayed with the correct characteristics. + (read-only/read-write from SET or START; + isolation-level from SET only, snapshot from START only) + + c) A subsequent transaction started with START TRANSACTION + (which is legal syntax in lieu of COMMIT AND CHAIN in MySQL) + may add/modify the current one-shots: + + - It may set up a read-only/read-write one-shot. + This one-shot will override the value used in the previous + transaction (whether that came from the default or a one-shot), + and, like all one-shots currently do, it will carry over into + any subsequent transactions that don't explicitly override them + in turn. This behavior is not guaranteed in the docs and may + change in the future, but the tracker item should correctly + reflect whatever behavior a given version of mysqld implements. + + - It may also set up a WITH CONSISTENT SNAPSHOT one-shot. + This one-shot does not currently carry over into subsequent + transactions (meaning that with "traditional syntax", WITH + CONSISTENT SNAPSHOT can only be requested for the first part + of a transaction chain). Again, the tracker item should reflect + mysqld behavior. + + d) A subsequent transaction started using COMMIT AND CHAIN + (or, for that matter, BEGIN WORK, which is currently + legal and equivalent syntax in MySQL, or START TRANSACTION + sans options) will re-use any one-shots set up so far + (with SET before the first transaction started, and with + all subsequent STARTs), except for WITH CONSISTANT SNAPSHOT, + which will never be chained and only applies when explicitly + given. + + It bears noting that if we switch sessions in a follow-up + transaction, SET TRANSACTION would be illegal in the old + session (as a transaction is active), whereas in the target + session which is being prepared, it should be legal, as no + transaction (chain) should have started yet. + + Therefore, we are free to generate SET TRANSACTION as a replay + statement even for a transaction that isn't the first in an + ongoing chain. Consider + + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITED; + START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT; + # work + COMMIT AND CHAIN; + + If we switch away at this point, the replay in the new session + needs to be + + SET TRANSACTION ISOLATION LEVEL READ UNCOMMITED; + START TRANSACTION READ ONLY; + + When a transaction ends (COMMIT/ROLLBACK sans CHAIN), all + per-transaction characteristics are reset to the session's + defaults. + + This also holds for a transaction ended implicitly! (transaction.cc) + Once again, the aim is to have the tracker item reflect on a + given mysqld's actual behavior. + */ + + /* + "ISOLATION LEVEL" + Only legal in SET TRANSACTION, so will always be replayed as such. + */ + if (tx_isol_level != TX_ISOL_INHERIT) + { + /* + Unfortunately, we can't re-use tx_isolation_names / + tx_isolation_typelib as it hyphenates its items. + */ + buf->append(STRING_WITH_LEN("SET TRANSACTION ISOLATION LEVEL ")); + buf->append(isol[tx_isol_level - 1].str, isol[tx_isol_level - 1].length); + buf->append(STRING_WITH_LEN("; ")); + } + + /* + Start transaction will usually result in TX_EXPLICIT (transaction + started, but no data attached yet), except when WITH CONSISTENT + SNAPSHOT, in which case we may have data pending. + If it's an XA transaction, we don't go through here so we can + first print the trx access mode ("SET TRANSACTION READ ...") + separately before adding XA START (whereas with START TRANSACTION, + we can merge the access mode into the same statement). + */ + if ((tx_curr_state & TX_EXPLICIT) && !is_xa) + { + buf->append(STRING_WITH_LEN("START TRANSACTION")); + + /* + "WITH CONSISTENT SNAPSHOT" + Defaults to no, can only be enabled. + Only appears in START TRANSACTION. + */ + if (tx_curr_state & TX_WITH_SNAPSHOT) + { + buf->append(STRING_WITH_LEN(" WITH CONSISTENT SNAPSHOT")); + if (tx_read_flags != TX_READ_INHERIT) + buf->append(STRING_WITH_LEN(",")); + } + + /* + "READ WRITE / READ ONLY" can be set globally, per-session, + or just for one transaction. + + The latter case can take the form of + START TRANSACTION READ (WRITE|ONLY), or of + SET TRANSACTION READ (ONLY|WRITE). + (Both set thd->read_only for the upcoming transaction; + it will ultimately be re-set to the session default.) + + As the regular session-variable tracker does not monitor the one-shot, + we'll have to do it here. + + If READ is flagged as set explicitly (rather than just inherited + from the session's default), we'll get the actual bool from the THD. + */ + if (tx_read_flags != TX_READ_INHERIT) + { + if (tx_read_flags == TX_READ_ONLY) + buf->append(STRING_WITH_LEN(" READ ONLY")); + else + buf->append(STRING_WITH_LEN(" READ WRITE")); + } + buf->append(STRING_WITH_LEN("; ")); + } + else if (tx_read_flags != TX_READ_INHERIT) + { + /* + "READ ONLY" / "READ WRITE" + We could transform this to SET TRANSACTION even when it occurs + in START TRANSACTION, but for now, we'll resysynthesize the original + command as closely as possible. + */ + buf->append(STRING_WITH_LEN("SET TRANSACTION ")); + if (tx_read_flags == TX_READ_ONLY) + buf->append(STRING_WITH_LEN("READ ONLY; ")); + else + buf->append(STRING_WITH_LEN("READ WRITE; ")); + } + + if ((tx_curr_state & TX_EXPLICIT) && is_xa) + { + XID *xid= &thd->transaction.xid_state.xid; + long glen, blen; + + buf->append(STRING_WITH_LEN("XA START")); + + if ((glen= xid->gtrid_length) > 0) + { + buf->append(STRING_WITH_LEN(" '")); + buf->append(xid->data, glen); + + if ((blen= xid->bqual_length) > 0) + { + buf->append(STRING_WITH_LEN("','")); + buf->append(xid->data + glen, blen); + } + buf->append(STRING_WITH_LEN("'")); + + if (xid->formatID != 1) + { + buf->append(STRING_WITH_LEN(",")); + buf->append_ulonglong(xid->formatID); + } + } + + buf->append(STRING_WITH_LEN("; ")); + } + + // discard trailing space + if (buf->length() > start) + buf->length(buf->length() - 1); + } + + { + ulonglong length= buf->length() - start; + uchar *place= (uchar *)(buf->ptr() + (start - 2)); + DBUG_ASSERT(length < 249); // in fact < 110 + DBUG_ASSERT(start >= 3); + + DBUG_ASSERT((place - 1)[0] == SESSION_TRACK_TRANSACTION_CHARACTERISTICS); + /* Length of the overall entity. */ + place[0]= length + 1; + /* Transaction characteristics (length-encoded string). */ + place[1]= length; + } + } + + reset(); + + return false; +} + + +/** + Reset the m_changed flag for next statement. +*/ + +void Transaction_state_tracker::reset() +{ + m_changed= false; + tx_reported_state= tx_curr_state; + tx_changed= TX_CHG_NONE; +} + + +/** + Helper function: turn table info into table access flag. + Accepts table lock type and engine type flag (transactional/ + non-transactional), and returns the corresponding access flag + out of TX_READ_TRX, TX_READ_UNSAFE, TX_WRITE_TRX, TX_WRITE_UNSAFE. + + @param thd [IN] The thd handle + @param set [IN] The table's access/lock type + @param set [IN] Whether the table's engine is transactional + + @return The table access flag +*/ + +enum_tx_state Transaction_state_tracker::calc_trx_state(THD *thd, + thr_lock_type l, + bool has_trx) +{ + enum_tx_state s; + bool read= (l <= TL_READ_NO_INSERT); + + if (read) + s= has_trx ? TX_READ_TRX : TX_READ_UNSAFE; + else + s= has_trx ? TX_WRITE_TRX : TX_WRITE_UNSAFE; + + return s; +} + + +/** + Register the end of an (implicit or explicit) transaction. + + @param thd [IN] The thd handle +*/ +void Transaction_state_tracker::end_trx(THD *thd) +{ + DBUG_ASSERT(thd->variables.session_track_transaction_info > TX_TRACK_NONE); + + if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) + return; + + if (tx_curr_state != TX_EMPTY) + { + if (tx_curr_state & TX_EXPLICIT) + tx_changed |= TX_CHG_CHISTICS; + tx_curr_state &= TX_LOCKED_TABLES; + } + update_change_flags(thd); +} + + +/** + Clear flags pertaining to the current statement or transaction. + May be called repeatedly within the same execution cycle. + + @param thd [IN] The thd handle. + @param set [IN] The flags to clear +*/ + +void Transaction_state_tracker::clear_trx_state(THD *thd, uint clear) +{ + if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) + return; + + tx_curr_state &= ~clear; + update_change_flags(thd); +} + + +/** + Add flags pertaining to the current statement or transaction. + May be called repeatedly within the same execution cycle, + e.g. to add access info for more tables. + + @param thd [IN] The thd handle. + @param set [IN] The flags to add +*/ + +void Transaction_state_tracker::add_trx_state(THD *thd, uint add) +{ + if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) + return; + + if (add == TX_EXPLICIT) + { + /* Always send characteristic item (if tracked), always replace state. */ + tx_changed |= TX_CHG_CHISTICS; + tx_curr_state = TX_EXPLICIT; + } + + /* + If we're not in an implicit or explicit transaction, but + autocommit==0 and tables are accessed, we flag "implicit transaction." + */ + else if (!(tx_curr_state & (TX_EXPLICIT|TX_IMPLICIT)) && + (thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT) && + (add & + (TX_READ_TRX | TX_READ_UNSAFE | TX_WRITE_TRX | TX_WRITE_UNSAFE))) + tx_curr_state |= TX_IMPLICIT; + + /* + Only flag state when in transaction or LOCK TABLES is added. + */ + if ((tx_curr_state & (TX_EXPLICIT | TX_IMPLICIT)) || + (add & TX_LOCKED_TABLES)) + tx_curr_state |= add; + + update_change_flags(thd); +} + + +/** + Add "unsafe statement" flag if applicable. + + @param thd [IN] The thd handle. + @param set [IN] The flags to add +*/ + +void Transaction_state_tracker::add_trx_state_from_thd(THD *thd) +{ + if (m_enabled) + { + if (thd->lex->is_stmt_unsafe()) + add_trx_state(thd, TX_STMT_UNSAFE); + } +} + + +/** + Set read flags (read only/read write) pertaining to the next + transaction. + + @param thd [IN] The thd handle. + @param set [IN] The flags to set +*/ + +void Transaction_state_tracker::set_read_flags(THD *thd, + enum enum_tx_read_flags flags) +{ + if (m_enabled && (tx_read_flags != flags)) + { + tx_read_flags = flags; + tx_changed |= TX_CHG_CHISTICS; + mark_as_changed(thd, NULL); + } +} + + +/** + Set isolation level pertaining to the next transaction. + + @param thd [IN] The thd handle. + @param set [IN] The isolation level to set +*/ + +void Transaction_state_tracker::set_isol_level(THD *thd, + enum enum_tx_isol_level level) +{ + if (m_enabled && (tx_isol_level != level)) + { + tx_isol_level = level; + tx_changed |= TX_CHG_CHISTICS; + mark_as_changed(thd, NULL); + } +} + + +/////////////////////////////////////////////////////////////////////////////// + +Session_state_change_tracker::Session_state_change_tracker() +{ + m_changed= false; +} + +/** + @Enable/disable the tracker based on @@session_track_state_change value. + + @param thd [IN] The thd handle. + @return false (always) + +**/ + +bool Session_state_change_tracker::update(THD *thd, set_var *) +{ + m_enabled= thd->variables.session_track_state_change; + return false; +} + +/** + Store the '1' in the specified buffer when state is changed. + + @param thd [IN] The thd handle. + @paran buf [INOUT] Buffer to store the information to. + + @reval false Success + @retval true Error +**/ + +bool Session_state_change_tracker::store(THD *thd, String *buf) +{ + if (unlikely((1 + 1 + 1 + buf->length() >= MAX_PACKET_LENGTH) || + buf->reserve(1 + 1 + 1, EXTRA_ALLOC))) + return true; + + compile_time_assert(SESSION_TRACK_STATE_CHANGE < 251); + /* Session state type (SESSION_TRACK_STATE_CHANGE) */ + buf->q_append((char)SESSION_TRACK_STATE_CHANGE); + + /* Length of the overall entity (1 byte) */ + buf->q_append('\1'); + + DBUG_ASSERT(is_state_changed(thd)); + buf->q_append('1'); + + reset(); + + return false; +} + + +/** + Reset the m_changed flag for next statement. +*/ + +void Session_state_change_tracker::reset() +{ + m_changed= false; +} + + +/** + Find if there is a session state change. +*/ + +bool Session_state_change_tracker::is_state_changed(THD *) +{ + return m_changed; +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + @brief Initialize session tracker objects. +*/ + +Session_tracker::Session_tracker() +{ + /* track data ID fit into one byte in net coding */ + compile_time_assert(SESSION_TRACK_always_at_the_end < 251); + /* one tracker could serv several tracking data */ + compile_time_assert((uint)SESSION_TRACK_always_at_the_end >= + (uint)SESSION_TRACKER_END); + + for (int i= 0; i < SESSION_TRACKER_END; i++) + m_trackers[i]= NULL; +} + + +/** + @brief Enables the tracker objects. + + @param thd [IN] The thread handle. + + @return void +*/ + +void Session_tracker::enable(THD *thd) +{ + /* + Originally and correctly this allocation was in the constructor and + deallocation in the destructor, but in this case memory counting + system works incorrectly (for example in INSERT DELAYED thread) + */ + deinit(); + m_trackers[SESSION_SYSVARS_TRACKER]= + new (std::nothrow) Session_sysvars_tracker(); + m_trackers[CURRENT_SCHEMA_TRACKER]= + new (std::nothrow) Current_schema_tracker; + m_trackers[SESSION_STATE_CHANGE_TRACKER]= + new (std::nothrow) Session_state_change_tracker; + m_trackers[SESSION_GTIDS_TRACKER]= + new (std::nothrow) Not_implemented_tracker; + m_trackers[TRANSACTION_INFO_TRACKER]= + new (std::nothrow) Transaction_state_tracker; + + for (int i= 0; i < SESSION_TRACKER_END; i++) + m_trackers[i]->enable(thd); +} + + +/** + Method called during the server startup to verify the contents + of @@session_track_system_variables. + + @retval false Success + @retval true Failure +*/ + +bool Session_tracker::server_boot_verify(CHARSET_INFO *char_set) +{ + bool result; + LEX_STRING tmp; + tmp.str= global_system_variables.session_track_system_variables; + tmp.length= safe_strlen(tmp.str); + result= + Session_sysvars_tracker::server_init_check(NULL, char_set, tmp); + return result; +} + + +/** + @brief Store all change information in the specified buffer. + + @param thd [IN] The thd handle. + @param buf [OUT] Reference to the string buffer to which the state + change data needs to be written. +*/ + +void Session_tracker::store(THD *thd, String *buf) +{ + size_t start; + + /* + Probably most track result will fit in 251 byte so lets made it at + least efficient. We allocate 1 byte for length and then will move + string if there is more. + */ + buf->append('\0'); + start= buf->length(); + + /* Get total length. */ + for (int i= 0; i < SESSION_TRACKER_END; i++) + { + if (m_trackers[i]->is_changed() && + m_trackers[i]->store(thd, buf)) + { + buf->length(start); // it is safer to have 0-length block in case of error + return; + } + } + + size_t length= buf->length() - start; + uchar *data= (uchar *)(buf->ptr() + start); + uint size; + + if ((size= net_length_size(length)) != 1) + { + if (buf->reserve(size - 1, EXTRA_ALLOC)) + { + buf->length(start); // it is safer to have 0-length block in case of error + return; + } + memmove(data + (size - 1), data, length); + } + + net_store_length(data - 1, length); +} + +#endif //EMBEDDED_LIBRARY diff --git a/sql/session_tracker.h b/sql/session_tracker.h new file mode 100644 index 00000000000..3f73b5dc705 --- /dev/null +++ b/sql/session_tracker.h @@ -0,0 +1,304 @@ +#ifndef SESSION_TRACKER_INCLUDED +#define SESSION_TRACKER_INCLUDED + +/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2016, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "m_string.h" +#include "thr_lock.h" + +#ifndef EMBEDDED_LIBRARY +/* forward declarations */ +class THD; +class set_var; +class String; + + +enum enum_session_tracker +{ + SESSION_SYSVARS_TRACKER, /* Session system variables */ + CURRENT_SCHEMA_TRACKER, /* Current schema */ + SESSION_STATE_CHANGE_TRACKER, + SESSION_GTIDS_TRACKER, /* Tracks GTIDs */ + TRANSACTION_INFO_TRACKER, /* Transaction state */ + SESSION_TRACKER_END /* must be the last */ +}; + +/** + State_tracker + + An abstract class that defines the interface for any of the server's + 'session state change tracker'. A tracker, however, is a sub- class of + this class which takes care of tracking the change in value of a part- + icular session state type and thus defines various methods listed in this + interface. The change information is later serialized and transmitted to + the client through protocol's OK packet. + + Tracker system variables :- + A tracker is normally mapped to a system variable. So in order to enable, + disable or modify the sub-entities of a tracker, the user needs to modify + the respective system variable either through SET command or via command + line option. As required in system variable handling, this interface also + includes two functions to help in the verification of the supplied value + (ON_UPDATE) of the tracker system variable, namely - update(). +*/ + +class State_tracker +{ +protected: + /** + Is tracking enabled for a particular session state type ? + + @note: it is a cache of the corresponding thd->variables.session_track_xxx + variable + */ + bool m_enabled; + + /** Has the session state type changed ? */ + bool m_changed; + +public: + /** Constructor */ + State_tracker() : m_enabled(false), m_changed(false) + {} + + /** Destructor */ + virtual ~State_tracker() + {} + + /** Getters */ + bool is_enabled() const + { return m_enabled; } + + bool is_changed() const + { return m_changed; } + + /** Called in the constructor of THD*/ + virtual bool enable(THD *thd)= 0; + + /** To be invoked when the tracker's system variable is updated (ON_UPDATE).*/ + virtual bool update(THD *thd, set_var *var)= 0; + + /** Store changed data into the given buffer. */ + virtual bool store(THD *thd, String *buf)= 0; + + /** Mark the entity as changed. */ + virtual void mark_as_changed(THD *thd, LEX_CSTRING *name); +}; + +bool sysvartrack_validate_value(THD *thd, const char *str, size_t len); +bool sysvartrack_reprint_value(THD *thd, char *str, size_t len); +bool sysvartrack_update(THD *thd, set_var *var); +size_t sysvartrack_value_len(THD *thd); +bool sysvartrack_value_construct(THD *thd, char *val, size_t len); + + +/** + Session_tracker + + This class holds an object each for all tracker classes and provides + methods necessary for systematic detection and generation of session + state change information. +*/ + +class Session_tracker +{ +private: + State_tracker *m_trackers[SESSION_TRACKER_END]; + + /* The following two functions are private to disable copying. */ + Session_tracker(Session_tracker const &other) + { + DBUG_ASSERT(FALSE); + } + Session_tracker& operator= (Session_tracker const &rhs) + { + DBUG_ASSERT(FALSE); + return *this; + } + +public: + + Session_tracker(); + ~Session_tracker() + { + deinit(); + } + + /* trick to make happy memory accounting system */ + void deinit() + { + for (int i= 0; i < SESSION_TRACKER_END; i++) + { + if (m_trackers[i]) + delete m_trackers[i]; + m_trackers[i]= NULL; + } + } + + void enable(THD *thd); + static bool server_boot_verify(CHARSET_INFO *char_set); + + /** Returns the pointer to the tracker object for the specified tracker. */ + inline State_tracker *get_tracker(enum_session_tracker tracker) const + { + return m_trackers[tracker]; + } + + inline void mark_as_changed(THD *thd, enum enum_session_tracker tracker, + LEX_CSTRING *data) + { + if (m_trackers[tracker]->is_enabled()) + m_trackers[tracker]->mark_as_changed(thd, data); + } + + + void store(THD *thd, String *main_buf); +}; + + +/* + Transaction_state_tracker +*/ + +/** + Transaction state (no transaction, transaction active, work attached, etc.) +*/ +enum enum_tx_state { + TX_EMPTY = 0, ///< "none of the below" + TX_EXPLICIT = 1, ///< an explicit transaction is active + TX_IMPLICIT = 2, ///< an implicit transaction is active + TX_READ_TRX = 4, ///< transactional reads were done + TX_READ_UNSAFE = 8, ///< non-transaction reads were done + TX_WRITE_TRX = 16, ///< transactional writes were done + TX_WRITE_UNSAFE = 32, ///< non-transactional writes were done + TX_STMT_UNSAFE = 64, ///< "unsafe" (non-deterministic like UUID()) stmts + TX_RESULT_SET = 128, ///< result set was sent + TX_WITH_SNAPSHOT= 256, ///< WITH CONSISTENT SNAPSHOT was used + TX_LOCKED_TABLES= 512 ///< LOCK TABLES is active +}; + + +/** + Transaction access mode +*/ +enum enum_tx_read_flags { + TX_READ_INHERIT = 0, ///< not explicitly set, inherit session.tx_read_only + TX_READ_ONLY = 1, ///< START TRANSACTION READ ONLY, or tx_read_only=1 + TX_READ_WRITE = 2, ///< START TRANSACTION READ WRITE, or tx_read_only=0 +}; + + +/** + Transaction isolation level +*/ +enum enum_tx_isol_level { + TX_ISOL_INHERIT = 0, ///< not explicitly set, inherit session.tx_isolation + TX_ISOL_UNCOMMITTED = 1, + TX_ISOL_COMMITTED = 2, + TX_ISOL_REPEATABLE = 3, + TX_ISOL_SERIALIZABLE= 4 +}; + + +/** + Transaction tracking level +*/ +enum enum_session_track_transaction_info { + TX_TRACK_NONE = 0, ///< do not send tracker items on transaction info + TX_TRACK_STATE = 1, ///< track transaction status + TX_TRACK_CHISTICS = 2 ///< track status and characteristics +}; + + +/** + This is a tracker class that enables & manages the tracking of + current transaction info for a particular connection. +*/ + +class Transaction_state_tracker : public State_tracker +{ +private: + /** Helper function: turn table info into table access flag */ + enum_tx_state calc_trx_state(THD *thd, thr_lock_type l, bool has_trx); +public: + /** Constructor */ + Transaction_state_tracker(); + bool enable(THD *thd) + { return update(thd, NULL); } + bool update(THD *thd, set_var *var); + bool store(THD *thd, String *buf); + + /** Change transaction characteristics */ + void set_read_flags(THD *thd, enum enum_tx_read_flags flags); + void set_isol_level(THD *thd, enum enum_tx_isol_level level); + + /** Change transaction state */ + void clear_trx_state(THD *thd, uint clear); + void add_trx_state(THD *thd, uint add); + void inline add_trx_state(THD *thd, thr_lock_type l, bool has_trx) + { + add_trx_state(thd, calc_trx_state(thd, l, has_trx)); + } + void add_trx_state_from_thd(THD *thd); + void end_trx(THD *thd); + + +private: + enum enum_tx_changed { + TX_CHG_NONE = 0, ///< no changes from previous stmt + TX_CHG_STATE = 1, ///< state has changed from previous stmt + TX_CHG_CHISTICS = 2 ///< characteristics have changed from previous stmt + }; + + /** any trackable changes caused by this statement? */ + uint tx_changed; + + /** transaction state */ + uint tx_curr_state, tx_reported_state; + + /** r/w or r/o set? session default? */ + enum enum_tx_read_flags tx_read_flags; + + /** isolation level */ + enum enum_tx_isol_level tx_isol_level; + + void reset(); + + inline void update_change_flags(THD *thd) + { + tx_changed &= ~TX_CHG_STATE; + tx_changed |= (tx_curr_state != tx_reported_state) ? TX_CHG_STATE : 0; + if (tx_changed != TX_CHG_NONE) + mark_as_changed(thd, NULL); + } +}; + +#define TRANSACT_TRACKER(X) \ + do { if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) \ + {((Transaction_state_tracker *) \ + thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER)) \ + ->X; } } while(0) +#define SESSION_TRACKER_CHANGED(A,B,C) \ + thd->session_tracker.mark_as_changed(A,B,C) +#else + +#define TRANSACT_TRACKER(X) do{}while(0) +#define SESSION_TRACKER_CHANGED(A,B,C) do{}while(0) + +#endif //EMBEDDED_LIBRARY + +#endif /* SESSION_TRACKER_INCLUDED */ diff --git a/sql/set_var.cc b/sql/set_var.cc index b178681e952..26eb5127a0b 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -115,6 +115,9 @@ void sys_var_end() DBUG_VOID_RETURN; } + +static bool static_test_load= TRUE; + /** sys_var constructor @@ -184,6 +187,8 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, else chain->first= this; chain->last= this; + + test_load= &static_test_load; } bool sys_var::update(THD *thd, set_var *var) @@ -204,8 +209,28 @@ bool sys_var::update(THD *thd, set_var *var) (on_update && on_update(this, thd, OPT_GLOBAL)); } else - return session_update(thd, var) || + { + bool ret= session_update(thd, var) || (on_update && on_update(this, thd, OPT_SESSION)); + + /* + Make sure we don't session-track variables that are not actually + part of the session. tx_isolation and and tx_read_only for example + exist as GLOBAL, SESSION, and one-shot ("for next transaction only"). + */ + if ((var->type == OPT_SESSION) && (!ret)) + { + SESSION_TRACKER_CHANGED(thd, SESSION_SYSVARS_TRACKER, + (LEX_CSTRING*)var->var); + /* + Here MySQL sends variable name to avoid reporting change of + the tracker itself, but we decided that it is not needed + */ + SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL); + } + + return ret; + } } uchar *sys_var::session_value_ptr(THD *thd, const LEX_STRING *base) @@ -867,6 +892,8 @@ int set_var_user::update(THD *thd) MYF(0)); return -1; } + + SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL); return 0; } @@ -914,7 +941,11 @@ int set_var_role::check(THD *thd) int set_var_role::update(THD *thd) { #ifndef NO_EMBEDDED_ACCESS_CHECKS - return acl_setrole(thd, role.str, access); + int res= acl_setrole(thd, role.str, access); + if (!res) + thd->session_tracker.mark_as_changed(thd, SESSION_STATE_CHANGE_TRACKER, + NULL); + return res; #else return 0; #endif @@ -968,6 +999,33 @@ int set_var_collation_client::update(THD *thd) { thd->update_charset(character_set_client, collation_connection, character_set_results); + + /* Mark client collation variables as changed */ +#ifndef EMBEDDED_LIBRARY + if (thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER)->is_enabled()) + { + sys_var *svar; + mysql_mutex_lock(&LOCK_plugin); + if ((svar= find_sys_var_ex(thd, "character_set_client", + sizeof("character_set_client") - 1, + false, true))) + thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER)-> + mark_as_changed(thd, (LEX_CSTRING*)svar); + if ((svar= find_sys_var_ex(thd, "character_set_results", + sizeof("character_set_results") - 1, + false, true))) + thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER)-> + mark_as_changed(thd, (LEX_CSTRING*)svar); + if ((svar= find_sys_var_ex(thd, "character_set_connection", + sizeof("character_set_connection") - 1, + false, true))) + thd->session_tracker.get_tracker(SESSION_SYSVARS_TRACKER)-> + mark_as_changed(thd, (LEX_CSTRING*)svar); + mysql_mutex_unlock(&LOCK_plugin); + } + thd->session_tracker.mark_as_changed(thd, SESSION_STATE_CHANGE_TRACKER, NULL); +#endif //EMBEDDED_LIBRARY + thd->protocol_text.init(thd); thd->protocol_binary.init(thd); return 0; diff --git a/sql/set_var.h b/sql/set_var.h index 060a4e1a57c..ba8027edc72 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -48,6 +48,7 @@ struct sys_var_chain int mysql_add_sys_var_chain(sys_var *chain); int mysql_del_sys_var_chain(sys_var *chain); + /** A class representing one system variable - that is something that can be accessed as @@global.variable_name or @@session.variable_name, @@ -60,6 +61,7 @@ class sys_var: protected Value_source // for double_from_string_with_check public: sys_var *next; LEX_CSTRING name; + bool *test_load; enum flag_enum { GLOBAL, SESSION, ONLY_SESSION, SCOPE_MASK=1023, READONLY=1024, ALLOCATED=2048, PARSE_EARLY=4096, NO_SET_STATEMENT=8192, AUTO_SET=16384}; @@ -240,6 +242,9 @@ protected: uchar *global_var_ptr() { return ((uchar*)&global_system_variables) + offset; } + + friend class Session_sysvars_tracker; + friend class Session_tracker; }; #include "sql_plugin.h" /* SHOW_HA_ROWS, SHOW_MY_BOOL */ @@ -385,7 +390,7 @@ extern SHOW_COMP_OPTION have_openssl; SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type); int fill_sysvars(THD *thd, TABLE_LIST *tables, COND *cond); -sys_var *find_sys_var(THD *thd, const char *str, uint length=0); +sys_var *find_sys_var(THD *thd, const char *str, size_t length=0); int sql_set_variables(THD *thd, List *var_list, bool free); #define SYSVAR_AUTOSIZE(VAR,VAL) \ diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index e60c85d17c6..43de678e1c2 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -4064,21 +4064,21 @@ ER_LOCK_OR_ACTIVE_TRANSACTION swe "Kan inte utföra kommandot emedan du har en låst tabell eller an aktiv transaktion" ukr "Не можу виконати подану команду тому, що таблиця заблокована або виконується транзакція" ER_UNKNOWN_SYSTEM_VARIABLE - cze "Neznámá systémová proměnná '%-.64s'" - dan "Ukendt systemvariabel '%-.64s'" - nla "Onbekende systeem variabele '%-.64s'" - eng "Unknown system variable '%-.64s'" - est "Tundmatu süsteemne muutuja '%-.64s'" - fre "Variable système '%-.64s' inconnue" - ger "Unbekannte Systemvariable '%-.64s'" - ita "Variabile di sistema '%-.64s' sconosciuta" - jpn "'%-.64s' は不明なシステム変数です。" - por "Variável de sistema '%-.64s' desconhecida" - rus "Неизвестная системная переменная '%-.64s'" - serbian "Nepoznata sistemska promenljiva '%-.64s'" - spa "Desconocida variable de sistema '%-.64s'" - swe "Okänd systemvariabel: '%-.64s'" - ukr "Невідома системна змінна '%-.64s'" + cze "Neznámá systémová proměnná '%-.*s'" + dan "Ukendt systemvariabel '%-.*s'" + nla "Onbekende systeem variabele '%-.*s'" + eng "Unknown system variable '%-.*s'" + est "Tundmatu süsteemne muutuja '%-.*s'" + fre "Variable système '%-.*s' inconnue" + ger "Unbekannte Systemvariable '%-.*s'" + ita "Variabile di sistema '%-.*s' sconosciuta" + jpn "'%-.*s' は不明なシステム変数です。" + por "Variável de sistema '%-.*s' desconhecida" + rus "Неизвестная системная переменная '%-.*s'" + serbian "Nepoznata sistemska promenljiva '%-.*s'" + spa "Desconocida variable de sistema '%-.*s'" + swe "Okänd systemvariabel: '%-.*s'" + ukr "Невідома системна змінна '%-.*s'" ER_CRASHED_ON_USAGE cze "Tabulka '%-.192s' je označena jako porušená a měla by být opravena" dan "Tabellen '%-.192s' er markeret med fejl og bør repareres" @@ -7139,7 +7139,6 @@ ER_KILL_QUERY_DENIED_ERROR ER_NO_EIS_FOR_FIELD eng "Engine-independent statistics are not collected for column '%s'" ukr "Незалежна від типу таблиці статистика не збирається для стовбця '%s'" - # # Internal errors, not used # diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 6b048cec68b..41006f07a0a 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -2048,6 +2048,8 @@ sp_head::execute_procedure(THD *thd, List *args) break; } } + + TRANSACT_TRACKER(add_trx_state_from_thd(thd)); } /* @@ -2977,6 +2979,18 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, reinit_stmt_before_use(thd, m_lex); +#ifndef EMBEDDED_LIBRARY + /* + If there was instruction which changed tracking state, + the result of changed tracking state send to client in OK packed. + So it changes result sent to client and probably can be different + independent on query text. So we can't cache such results. + */ + if ((thd->client_capabilities & CLIENT_SESSION_TRACK) && + (thd->server_status & SERVER_SESSION_STATE_CHANGED)) + thd->lex->safe_to_cache_query= 0; +#endif + if (open_tables) res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables); @@ -3053,6 +3067,9 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, what is needed from the substatement gained */ thd->transaction.stmt.modified_non_trans_table |= parent_modified_non_trans_table; + + TRANSACT_TRACKER(add_trx_state_from_thd(thd)); + /* Unlike for PS we should not call Item's destructors for newly created items after execution of each instruction in stored routine. This is diff --git a/sql/sql_base.cc b/sql/sql_base.cc index d7812db53bd..7757068b265 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2093,6 +2093,9 @@ Locked_tables_list::init_locked_tables(THD *thd) return TRUE; } } + + TRANSACT_TRACKER(add_trx_state(thd, TX_LOCKED_TABLES)); + thd->enter_locked_tables_mode(LTM_LOCK_TABLES); return FALSE; @@ -2133,6 +2136,8 @@ Locked_tables_list::unlock_locked_tables(THD *thd) } thd->leave_locked_tables_mode(); + TRANSACT_TRACKER(clear_trx_state(thd, TX_LOCKED_TABLES)); + DBUG_ASSERT(thd->transaction.stmt.is_empty()); close_thread_tables(thd); @@ -4354,6 +4359,13 @@ static bool check_lock_and_start_stmt(THD *thd, table_list->table->file->print_error(error, MYF(0)); DBUG_RETURN(1); } + + /* + Record in transaction state tracking + */ + TRANSACT_TRACKER(add_trx_state(thd, lock_type, + table_list->table->file->has_transactions())); + DBUG_RETURN(0); } diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 06c5f992939..8ff4684f0ff 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1381,6 +1381,21 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) DBUG_VOID_RETURN; } + /* + Do not store queries while tracking transaction state. + The tracker already flags queries that actually have + transaction tracker items, but this will make behavior + more straight forward. + */ +#ifndef EMBEDDED_LIBRARY + if (thd->variables.session_track_transaction_info != TX_TRACK_NONE) + { + DBUG_PRINT("qcache", ("Do not work with transaction tracking")); + DBUG_VOID_RETURN; + } +#endif //EMBEDDED_LIBRARY + + /* The following assert fails if we haven't called send_result_to_client */ DBUG_ASSERT(thd->base_query.is_alloced() || thd->base_query.ptr() == thd->query()); @@ -1719,6 +1734,20 @@ Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length) goto err; } + /* + Don't allow serving from Query_cache while tracking transaction + state. This is a safeguard in case an otherwise matching query + was added to the cache before tracking was turned on. + */ +#ifndef EMBEDDED_LIBRARY + if (thd->variables.session_track_transaction_info != TX_TRACK_NONE) + { + DBUG_PRINT("qcache", ("Do not work with transaction tracking")); + goto err; + } +#endif //EMBEDDED_LIBRARY + + thd->query_cache_is_applicable= 1; sql= org_sql; sql_end= sql + query_length; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index d29dc0eff14..54bc4b9959f 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1465,6 +1465,11 @@ void THD::init(void) /* Initialize the Debug Sync Facility. See debug_sync.cc. */ debug_sync_init_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ + +#ifndef EMBEDDED_LIBRARY + session_tracker.enable(this); +#endif //EMBEDDED_LIBRARY + apc_target.init(&LOCK_thd_data); DBUG_VOID_RETURN; } @@ -1763,6 +1768,12 @@ THD::~THD() lf_hash_put_pins(xid_hash_pins); /* Ensure everything is freed */ status_var.local_memory_used-= sizeof(THD); + + /* trick to make happy memory accounting system */ +#ifndef EMBEDDED_LIBRARY + session_tracker.deinit(); +#endif //EMBEDDED_LIBRARY + if (status_var.local_memory_used != 0) { DBUG_PRINT("error", ("memory_used: %lld", status_var.local_memory_used)); diff --git a/sql/sql_class.h b/sql/sql_class.h index 3c5cc46656b..02a0523bee2 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -45,6 +45,7 @@ #include #include #include +#include "session_tracker.h" extern "C" void set_thd_stage_info(void *thd, @@ -690,6 +691,11 @@ typedef struct system_variables my_bool pseudo_slave_mode; + char *session_track_system_variables; + ulong session_track_transaction_info; + my_bool session_track_schema; + my_bool session_track_state_change; + } SV; /** @@ -4056,6 +4062,9 @@ private: LEX_STRING invoker_host; public: +#ifndef EMBEDDED_LIBRARY + Session_tracker session_tracker; +#endif //EMBEDDED_LIBRARY /* Flag, mutex and condition for a thread to wait for a signal from another thread. @@ -4289,6 +4298,8 @@ my_eof(THD *thd) { thd->set_row_count_func(-1); thd->get_stmt_da()->set_eof_status(thd); + + TRANSACT_TRACKER(add_trx_state(thd, TX_RESULT_SET)); } #define tmp_disable_binlog(A) \ diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 1a0ee03ec34..20538fe1fb4 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1035,7 +1035,10 @@ exit: it to 0. */ if (thd->db && cmp_db_names(thd->db, db) && !error) + { mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server); + SESSION_TRACKER_CHANGED(thd, CURRENT_SCHEMA_TRACKER, NULL); + } my_dirend(dirp); DBUG_RETURN(error); } @@ -1459,7 +1462,7 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch) mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server); - DBUG_RETURN(FALSE); + goto done; } else { @@ -1476,8 +1479,7 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch) mysql_change_db_impl(thd, &INFORMATION_SCHEMA_NAME, SELECT_ACL, system_charset_info); - - DBUG_RETURN(FALSE); + goto done; } /* @@ -1564,8 +1566,7 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch) mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server); /* The operation succeed. */ - - DBUG_RETURN(FALSE); + goto done; } else { @@ -1589,6 +1590,9 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch) mysql_change_db_impl(thd, &new_db_file_name, db_access, db_default_cl); +done: + SESSION_TRACKER_CHANGED(thd, CURRENT_SCHEMA_TRACKER, NULL); + SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL); DBUG_RETURN(FALSE); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index c152984876e..c76e22a2e57 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1476,13 +1476,16 @@ uint maria_multi_check(THD *thd, char *packet, uint packet_length) DBUG_ENTER("maria_multi_check"); while (packet_length) { + char *packet_start= packet; + size_t subpacket_length= net_field_length((uchar **)&packet_start); + uint length_length= packet_start - packet; // length of command + 3 bytes where that length was stored - uint subpacket_length= (uint3korr(packet) + 3); - DBUG_PRINT("info", ("sub-packet length: %d command: %x", - subpacket_length, packet[3])); + DBUG_PRINT("info", ("sub-packet length: %ld + %d command: %x", + (ulong)subpacket_length, length_length, + packet_start[3])); - if (subpacket_length == 3 || - subpacket_length > packet_length) + if (subpacket_length == 0 || + (subpacket_length + length_length) > packet_length) { my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), MYF(0)); @@ -1490,8 +1493,8 @@ uint maria_multi_check(THD *thd, char *packet, uint packet_length) } counter++; - packet+= subpacket_length; - packet_length-= subpacket_length; + packet= packet_start + subpacket_length; + packet_length-= (subpacket_length + length_length); } DBUG_RETURN(counter); } @@ -2231,8 +2234,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; { + char *packet_start= packet; /* We have to store next length because it will be destroyed by '\0' */ - uint next_subpacket_length= uint3korr(packet); + size_t next_subpacket_length= net_field_length((uchar **)&packet_start); + uint next_length_length= packet_start - packet; unsigned char *readbuff= net->buff; if (net_allocate_new_packet(net, thd, MYF(0))) @@ -2246,13 +2251,19 @@ bool dispatch_command(enum enum_server_command command, THD *thd, while (packet_length) { current_com++; - uint subpacket_length= next_subpacket_length + 3; + size_t subpacket_length= next_subpacket_length + next_length_length; + uint length_length= next_length_length; if (subpacket_length < packet_length) - next_subpacket_length= uint3korr(packet + subpacket_length); + { + packet_start= packet + subpacket_length; + next_subpacket_length= net_field_length((uchar**)&packet_start); + next_length_length= packet_start - (packet + subpacket_length); + } /* safety like in do_command() */ packet[subpacket_length]= '\0'; - enum enum_server_command subcommand= fetch_command(thd, (packet + 3)); + enum enum_server_command subcommand= + fetch_command(thd, (packet + length_length)); if (server_command_flags[subcommand] & CF_NO_COM_MULTI) { @@ -2260,8 +2271,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, goto com_multi_end; } - if (dispatch_command(subcommand, thd, packet + (1 + 3), - subpacket_length - (1 + 3), TRUE, + if (dispatch_command(subcommand, thd, packet + (1 + length_length), + subpacket_length - (1 + length_length), TRUE, (current_com != counter))) { DBUG_ASSERT(thd->is_error()); @@ -3852,6 +3863,12 @@ mysql_execute_command(THD *thd) /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ if (create_info.tmp_table()) thd->variables.option_bits|= OPTION_KEEP_LOG; + /* in case of create temp tables if @@session_track_state_change is + ON then send session state notification in OK packet */ + if(create_info.options & HA_LEX_CREATE_TMP_TABLE) + { + SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL); + } my_ok(thd); } } @@ -4608,6 +4625,13 @@ end_with_restore_list: /* DDL and binlog write order are protected by metadata locks. */ res= mysql_rm_table(thd, first_table, lex->if_exists(), lex->tmp_table()); + + /* when dropping temporary tables if @@session_track_state_change is ON then + send the boolean tracker in the OK packet */ + if(!res && (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) + { + SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL); + } break; } case SQLCOM_SHOW_PROCESSLIST: @@ -5419,8 +5443,7 @@ end_with_restore_list: else { /* Reset the isolation level and access mode if no chaining transaction.*/ - thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; - thd->tx_read_only= thd->variables.tx_read_only; + trans_reset_one_shot_chistics(thd); } /* Disconnect the current client connection. */ if (tx_release) @@ -5467,8 +5490,7 @@ end_with_restore_list: else { /* Reset the isolation level and access mode if no chaining transaction.*/ - thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; - thd->tx_read_only= thd->variables.tx_read_only; + trans_reset_one_shot_chistics(thd); } /* Disconnect the current client connection. */ if (tx_release) @@ -5953,8 +5975,7 @@ end_with_restore_list: We've just done a commit, reset transaction isolation level and access mode to the session default. */ - thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; - thd->tx_read_only= thd->variables.tx_read_only; + trans_reset_one_shot_chistics(thd); my_ok(thd); break; } @@ -5972,8 +5993,7 @@ end_with_restore_list: We've just done a rollback, reset transaction isolation level and access mode to the session default. */ - thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; - thd->tx_read_only= thd->variables.tx_read_only; + trans_reset_one_shot_chistics(thd); my_ok(thd); break; } @@ -6191,6 +6211,9 @@ finish: { thd->mdl_context.release_statement_locks(); } + + TRANSACT_TRACKER(add_trx_state_from_thd(thd)); + WSREP_TO_ISOLATION_END; #ifdef WITH_WSREP diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 848358e517a..b715e33ae62 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -269,6 +269,7 @@ struct st_bookmark uint name_len; int offset; uint version; + bool loaded; char key[1]; }; @@ -1175,6 +1176,13 @@ err: DBUG_RETURN(errs > 0 || oks + dupes == 0); } +static void plugin_variables_deinit(struct st_plugin_int *plugin) +{ + + for (sys_var *var= plugin->system_vars; var; var= var->next) + (*var->test_load)= FALSE; + mysql_del_sys_var_chain(plugin->system_vars); +} static void plugin_deinitialize(struct st_plugin_int *plugin, bool ref_check) { @@ -1226,8 +1234,7 @@ static void plugin_deinitialize(struct st_plugin_int *plugin, bool ref_check) if (ref_check && plugin->ref_count) sql_print_error("Plugin '%s' has ref_count=%d after deinitialization.", plugin->name.str, plugin->ref_count); - - mysql_del_sys_var_chain(plugin->system_vars); + plugin_variables_deinit(plugin); } static void plugin_del(struct st_plugin_int *plugin) @@ -1447,7 +1454,7 @@ static int plugin_initialize(MEM_ROOT *tmp_root, struct st_plugin_int *plugin, err: if (ret) - mysql_del_sys_var_chain(plugin->system_vars); + plugin_variables_deinit(plugin); mysql_mutex_lock(&LOCK_plugin); plugin->state= state; @@ -2780,15 +2787,17 @@ static void update_func_double(THD *thd, struct st_mysql_sys_var *var, System Variables support ****************************************************************************/ - -sys_var *find_sys_var(THD *thd, const char *str, uint length) +sys_var *find_sys_var_ex(THD *thd, const char *str, size_t length, + bool throw_error, bool locked) { sys_var *var; sys_var_pluginvar *pi= NULL; plugin_ref plugin; - DBUG_ENTER("find_sys_var"); + DBUG_ENTER("find_sys_var_ex"); + DBUG_PRINT("enter", ("var '%.*s'", (int)length, str)); - mysql_mutex_lock(&LOCK_plugin); + if (!locked) + mysql_mutex_lock(&LOCK_plugin); mysql_rwlock_rdlock(&LOCK_system_variables_hash); if ((var= intern_find_sys_var(str, length)) && (pi= var->cast_pluginvar())) @@ -2807,14 +2816,20 @@ sys_var *find_sys_var(THD *thd, const char *str, uint length) } else mysql_rwlock_unlock(&LOCK_system_variables_hash); - mysql_mutex_unlock(&LOCK_plugin); + if (!locked) + mysql_mutex_unlock(&LOCK_plugin); - if (!var) - my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), (char*) str); + if (!throw_error && !var) + my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), (int)length, (char*) str); DBUG_RETURN(var); } +sys_var *find_sys_var(THD *thd, const char *str, size_t length) +{ + return find_sys_var_ex(thd, str, length, false, false); +} + /* called by register_var, construct_options and test_plugin_options. Returns the 'bookmark' for the named variable. @@ -3940,6 +3955,14 @@ my_bool mark_changed(int, const struct my_option *opt, char *) return 0; } +/** + It is always false to mark global plugin variable unloaded just to be + safe because we have no way now to know truth about them. + + TODO: make correct mechanism for global plugin variables +*/ +static bool static_unload= FALSE; + /** Create and register system variables supplied from the plugin and assigns initial values from corresponding command line arguments. @@ -4017,9 +4040,13 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, tmp_backup[tmp->nbackups++].save(&o->name); if ((var= find_bookmark(tmp->name.str, o->name, o->flags))) + { varname= var->key + 1; + var->loaded= TRUE; + } else { + var= NULL; len= tmp->name.length + strlen(o->name) + 2; varname= (char*) alloc_root(mem_root, len); strxmov(varname, tmp->name.str, "-", o->name, NullS); @@ -4027,6 +4054,9 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, convert_dash_to_underscore(varname, len-1); } v= new (mem_root) sys_var_pluginvar(&chain, varname, tmp, o); + v->test_load= (var ? &var->loaded : &static_unload); + DBUG_ASSERT(static_unload == FALSE); + if (!(o->flags & PLUGIN_VAR_NOCMDOPT)) { // update app_type, used for I_S.SYSTEM_VARIABLES diff --git a/sql/sql_plugin.h b/sql/sql_plugin.h index efa48b22ce8..47c3af83bdf 100644 --- a/sql/sql_plugin.h +++ b/sql/sql_plugin.h @@ -120,6 +120,8 @@ struct st_plugin_int }; +extern mysql_mutex_t LOCK_plugin; + /* See intern_plugin_lock() for the explanation for the conditionally defined plugin_ref type @@ -190,4 +192,6 @@ extern bool plugin_foreach_with_mask(THD *thd, plugin_foreach_func *func, extern bool plugin_dl_foreach(THD *thd, const LEX_STRING *dl, plugin_foreach_func *func, void *arg); +sys_var *find_sys_var_ex(THD *thd, const char *str, size_t length, + bool throw_error, bool locked); #endif diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index e8a7dce5771..eab2863588d 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2758,7 +2758,10 @@ void mysql_sql_stmt_prepare(THD *thd) thd->stmt_map.erase(stmt); } else + { + SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL); my_ok(thd, 0L, 0L, "Statement prepared"); + } DBUG_VOID_RETURN; } @@ -3208,6 +3211,7 @@ void mysql_sql_stmt_close(THD *thd) else { stmt->deallocate(); + SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL); my_ok(thd); } } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index a446e05d427..25af6fe07cc 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3212,6 +3212,132 @@ void remove_status_vars(SHOW_VAR *list) } +/** + @brief Returns the value of a system or a status variable. + + @param thd [in] The handle of the current THD. + @param variable [in] Details of the variable. + @param value_type [in] Variable type. + @param show_type [in] Variable show type. + @param charset [out] Character set of the value. + @param buff [in,out] Buffer to store the value. + (Needs to have enough memory + to hold the value of variable.) + @param length [out] Length of the value. + + @return Pointer to the value buffer. +*/ + +const char* get_one_variable(THD *thd, + const SHOW_VAR *variable, + enum_var_type value_type, SHOW_TYPE show_type, + system_status_var *status_var, + const CHARSET_INFO **charset, char *buff, + size_t *length) +{ + void *value= variable->value; + const char *pos= buff; + const char *end= buff; + + + if (show_type == SHOW_SYS) + { + sys_var *var= (sys_var *) value; + show_type= var->show_type(); + value= var->value_ptr(thd, value_type, &null_lex_str); + *charset= var->charset(thd); + } + + /* + note that value may be == buff. All SHOW_xxx code below + should still work in this case + */ + switch (show_type) { + case SHOW_DOUBLE_STATUS: + value= ((char *) status_var + (intptr) value); + /* fall through */ + case SHOW_DOUBLE: + /* 6 is the default precision for '%f' in sprintf() */ + end= buff + my_fcvt(*(double *) value, 6, buff, NULL); + break; + case SHOW_LONG_STATUS: + value= ((char *) status_var + (intptr) value); + /* fall through */ + case SHOW_ULONG: + case SHOW_LONG_NOFLUSH: // the difference lies in refresh_status() + end= int10_to_str(*(long*) value, buff, 10); + break; + case SHOW_LONGLONG_STATUS: + value= ((char *) status_var + (intptr) value); + /* fall through */ + case SHOW_ULONGLONG: + end= longlong10_to_str(*(longlong*) value, buff, 10); + break; + case SHOW_HA_ROWS: + end= longlong10_to_str((longlong) *(ha_rows*) value, buff, 10); + break; + case SHOW_BOOL: + end= strmov(buff, *(bool*) value ? "ON" : "OFF"); + break; + case SHOW_MY_BOOL: + end= strmov(buff, *(my_bool*) value ? "ON" : "OFF"); + break; + case SHOW_UINT: + end= int10_to_str((long) *(uint*) value, buff, 10); + break; + case SHOW_SINT: + end= int10_to_str((long) *(int*) value, buff, -10); + break; + case SHOW_SLONG: + end= int10_to_str(*(long*) value, buff, -10); + break; + case SHOW_SLONGLONG: + end= longlong10_to_str(*(longlong*) value, buff, -10); + break; + case SHOW_HAVE: + { + SHOW_COMP_OPTION tmp= *(SHOW_COMP_OPTION*) value; + pos= show_comp_option_name[(int) tmp]; + end= strend(pos); + break; + } + case SHOW_CHAR: + { + if (!(pos= (char*)value)) + pos= ""; + end= strend(pos); + break; + } + case SHOW_CHAR_PTR: + { + if (!(pos= *(char**) value)) + pos= ""; + + end= strend(pos); + break; + } + case SHOW_LEX_STRING: + { + LEX_STRING *ls=(LEX_STRING*)value; + if (!(pos= ls->str)) + end= pos= ""; + else + end= pos + ls->length; + break; + } + case SHOW_UNDEF: + break; // Return empty string + case SHOW_SYS: // Cannot happen + default: + DBUG_ASSERT(0); + break; + } + + *length= (size_t) (end - pos); + return pos; +} + + static bool show_status_array(THD *thd, const char *wild, SHOW_VAR *variables, enum enum_var_type scope, @@ -3324,109 +3450,21 @@ static bool show_status_array(THD *thd, const char *wild, name_buffer, wild))) && (!cond || cond->val_int())) { - void *value=var->value; - const char *pos, *end; // We assign a lot of const's + const char *pos; // We assign a lot of const's + size_t length; if (show_type == SHOW_SYS) - { - sys_var *var= (sys_var *) value; - show_type= var->show_type(); mysql_mutex_lock(&LOCK_global_system_variables); - value= var->value_ptr(thd, scope, &null_lex_str); - charset= var->charset(thd); - } + pos= get_one_variable(thd, var, scope, show_type, status_var, + &charset, buff, &length); - pos= end= buff; - /* - note that value may be == buff. All SHOW_xxx code below - should still work in this case - */ - switch (show_type) { - case SHOW_DOUBLE_STATUS: - value= ((char *) status_var + (intptr) value); - /* fall through */ - case SHOW_DOUBLE: - /* 6 is the default precision for '%f' in sprintf() */ - end= buff + my_fcvt(*(double *) value, 6, buff, NULL); - break; - case SHOW_LONG_STATUS: - value= ((char *) status_var + (intptr) value); - /* fall through */ - case SHOW_ULONG: - case SHOW_LONG_NOFLUSH: // the difference lies in refresh_status() - end= int10_to_str(*(long*) value, buff, 10); - break; - case SHOW_LONGLONG_STATUS: - value= ((char *) status_var + (intptr) value); - /* fall through */ - case SHOW_ULONGLONG: - end= longlong10_to_str(*(longlong*) value, buff, 10); - break; - case SHOW_HA_ROWS: - end= longlong10_to_str((longlong) *(ha_rows*) value, buff, 10); - break; - case SHOW_BOOL: - end= strmov(buff, *(bool*) value ? "ON" : "OFF"); - break; - case SHOW_MY_BOOL: - end= strmov(buff, *(my_bool*) value ? "ON" : "OFF"); - break; - case SHOW_UINT: - end= int10_to_str((long) *(uint*) value, buff, 10); - break; - case SHOW_SINT: - end= int10_to_str((long) *(int*) value, buff, -10); - break; - case SHOW_SLONG: - end= int10_to_str(*(long*) value, buff, -10); - break; - case SHOW_SLONGLONG: - end= longlong10_to_str(*(longlong*) value, buff, -10); - break; - case SHOW_HAVE: - { - SHOW_COMP_OPTION tmp= *(SHOW_COMP_OPTION*) value; - pos= show_comp_option_name[(int) tmp]; - end= strend(pos); - break; - } - case SHOW_CHAR: - { - if (!(pos= (char*)value)) - pos= ""; - end= strend(pos); - break; - } - case SHOW_CHAR_PTR: - { - if (!(pos= *(char**) value)) - pos= ""; - - end= strend(pos); - break; - } - case SHOW_LEX_STRING: - { - LEX_STRING *ls=(LEX_STRING*)value; - if (!(pos= ls->str)) - end= pos= ""; - else - end= pos + ls->length; - break; - } - case SHOW_UNDEF: - break; // Return empty string - case SHOW_SYS: // Cannot happen - default: - DBUG_ASSERT(0); - break; - } - table->field[1]->store(pos, (uint32) (end - pos), charset); + table->field[1]->store(pos, (uint32) length, charset); + thd->count_cuted_fields= CHECK_FIELD_IGNORE; table->field[1]->set_notnull(); - - if (var->type == SHOW_SYS) + if (show_type == SHOW_SYS) mysql_mutex_unlock(&LOCK_global_system_variables); + if (schema_table_store_record(thd, table)) { res= TRUE; diff --git a/sql/sql_show.h b/sql/sql_show.h index dbae2a42b39..e93b855450c 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -131,6 +131,12 @@ bool get_schema_tables_result(JOIN *join, enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table); TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list); +const char* get_one_variable(THD *thd, const SHOW_VAR *variable, + enum_var_type value_type, SHOW_TYPE show_type, + system_status_var *status_var, + const CHARSET_INFO **charset, char *buff, + size_t *length); + /* These functions were under INNODB_COMPATIBILITY_HOOKS */ int get_quote_char_for_identifier(THD *thd, const char *name, uint length); THD *find_thread_by_id(longlong id, bool query_id= false); diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 767154e019d..a5f266b2d2c 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -1,4 +1,5 @@ /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2016, MariaDB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1157,3 +1158,18 @@ uint convert_to_printable(char *to, size_t to_len, *t= '\0'; return t - to; } + +void String::q_net_store_length(ulonglong length) +{ + DBUG_ASSERT(Alloced_length >= (str_length + net_length_size(length))); + char *pos= (char *) net_store_length((uchar *)(Ptr + str_length), length); + str_length= pos - Ptr; +} + +void String::q_net_store_data(const uchar *from, size_t length) +{ + DBUG_ASSERT(Alloced_length >= (str_length + length + + net_length_size(length))); + q_net_store_length(length); + q_append((const char *)from, length); +} diff --git a/sql/sql_string.h b/sql/sql_string.h index 51a11c7a4ff..f53015fbd6b 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -359,7 +359,9 @@ public: if (ALIGN_SIZE(arg_length+1) < Alloced_length) { char *new_ptr; - if (!(new_ptr=(char*) my_realloc(Ptr,arg_length,MYF(0)))) + if (!(new_ptr=(char*) + my_realloc(Ptr, arg_length,MYF((thread_specific ? + MY_THREAD_SPECIFIC : 0))))) { Alloced_length = 0; real_alloc(arg_length); @@ -495,6 +497,11 @@ public: { Ptr[str_length++] = c; } + void q_append2b(const uint32 n) + { + int2store(Ptr + str_length, n); + str_length += 2; + } void q_append(const uint32 n) { int4store(Ptr + str_length, n); @@ -559,6 +566,7 @@ public: return Ptr+ old_length; /* Area to use */ } + inline bool append(const char *s, uint32 arg_length, uint32 step_alloc) { uint32 new_length= arg_length + str_length; @@ -623,6 +631,8 @@ public: { return !sortcmp(this, other, cs); } + void q_net_store_length(ulonglong length); + void q_net_store_data(const uchar *from, size_t length); }; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index c9194bcb276..7784a2b188a 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -55,6 +55,7 @@ #include "transaction.h" #include "sql_audit.h" + #ifdef __WIN__ #include #endif @@ -9228,6 +9229,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, { goto err_new_table_cleanup; } + /* in case of alter temp table send the tracker in OK packet */ + SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL); } diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 37a58af1439..ddceb67e1f4 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -3375,6 +3375,20 @@ bool Sys_var_tx_read_only::session_update(THD *thd, set_var *var) { // @see Sys_var_tx_isolation::session_update() above for the rules. thd->tx_read_only= var->save_result.ulonglong_value; + +#ifndef EMBEDDED_LIBRARY + if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) + { + Transaction_state_tracker *tst= (Transaction_state_tracker *) + thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER); + + if (var->type == OPT_DEFAULT) + tst->set_read_flags(thd, + thd->tx_read_only ? TX_READ_ONLY : TX_READ_WRITE); + else + tst->set_read_flags(thd, TX_READ_INHERIT); + } +#endif //EMBEDDED_LIBRARY } return false; } @@ -5386,3 +5400,78 @@ static Sys_var_ulong Sys_log_tc_size( DEFAULT(my_getpagesize() * 6), BLOCK_SIZE(my_getpagesize())); #endif + +#ifndef EMBEDDED_LIBRARY + +static Sys_var_sesvartrack Sys_track_session_sys_vars( + "session_track_system_variables", + "Track changes in registered system variables. " + "For compatibility with MySQL defaults this variable should be set to " + "\"autocommit, character_set_client, character_set_connection, " + "character_set_results, time_zone\"", + CMD_LINE(REQUIRED_ARG), IN_SYSTEM_CHARSET, + DEFAULT(""), + NO_MUTEX_GUARD); + +static bool update_session_track_schema(sys_var *self, THD *thd, + enum_var_type type) +{ + DBUG_ENTER("update_session_track_schema"); + DBUG_RETURN(thd->session_tracker.get_tracker(CURRENT_SCHEMA_TRACKER)-> + update(thd, NULL)); +} + +static Sys_var_mybool Sys_session_track_schema( + "session_track_schema", + "Track changes to the default schema.", + SESSION_VAR(session_track_schema), + CMD_LINE(OPT_ARG), DEFAULT(TRUE), + NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(0), + ON_UPDATE(update_session_track_schema)); + + +static bool update_session_track_tx_info(sys_var *self, THD *thd, + enum_var_type type) +{ + DBUG_ENTER("update_session_track_tx_info"); + DBUG_RETURN(thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER)-> + update(thd, NULL)); +} + +static const char *session_track_transaction_info_names[]= + { "OFF", "STATE", "CHARACTERISTICS", NullS }; + +static Sys_var_enum Sys_session_track_transaction_info( + "session_track_transaction_info", + "Track changes to the transaction attributes. OFF to disable; " + "STATE to track just transaction state (Is there an active transaction? " + "Does it have any data? etc.); CHARACTERISTICS to track transaction " + "state and report all statements needed to start a transaction with" + "the same characteristics (isolation level, read only/read write," + "snapshot - but not any work done / data modified within the " + "transaction).", + SESSION_VAR(session_track_transaction_info), + CMD_LINE(REQUIRED_ARG), session_track_transaction_info_names, + DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), + ON_UPDATE(update_session_track_tx_info)); + + +static bool update_session_track_state_change(sys_var *self, THD *thd, + enum_var_type type) +{ + DBUG_ENTER("update_session_track_state_change"); + DBUG_RETURN(thd->session_tracker.get_tracker(SESSION_STATE_CHANGE_TRACKER)-> + update(thd, NULL)); +} + +static Sys_var_mybool Sys_session_track_state_change( + "session_track_state_change", + "Track changes to the session state.", + SESSION_VAR(session_track_state_change), + CMD_LINE(OPT_ARG), DEFAULT(FALSE), + NO_MUTEX_GUARD, NOT_IN_BINLOG, + ON_CHECK(0), + ON_UPDATE(update_session_track_state_change)); + +#endif //EMBEDDED_LIBRARY diff --git a/sql/sys_vars.ic b/sql/sys_vars.ic index ca6634849a1..6f17e768d95 100644 --- a/sql/sys_vars.ic +++ b/sql/sys_vars.ic @@ -438,10 +438,10 @@ public: does not destroy individual members of SV, there's no way to free allocated string variables for every thread. */ -class Sys_var_charptr: public sys_var +class Sys_var_charptr_base: public sys_var { public: - Sys_var_charptr(const char *name_arg, + Sys_var_charptr_base(const char *name_arg, const char *comment, int flag_args, ptrdiff_t off, size_t size, CMD_LINE getopt, enum charset_enum is_os_charset_arg, @@ -463,8 +463,6 @@ public: */ option.var_type|= (flags & ALLOCATED) ? GET_STR_ALLOC : GET_STR; global_var(const char*)= def_val; - SYSVAR_ASSERT(scope() == GLOBAL); - SYSVAR_ASSERT(size == sizeof(char *)); } void cleanup() { @@ -503,31 +501,35 @@ public: } bool do_check(THD *thd, set_var *var) { return do_string_check(thd, var, charset(thd)); } - bool session_update(THD *thd, set_var *var) - { - DBUG_ASSERT(FALSE); - return true; - } - bool global_update(THD *thd, set_var *var) + bool session_update(THD *thd, set_var *var)= 0; + char *global_update_prepare(THD *thd, set_var *var) { char *new_val, *ptr= var->save_result.string_value.str; size_t len=var->save_result.string_value.length; if (ptr) { new_val= (char*)my_memdup(ptr, len+1, MYF(MY_WME)); - if (!new_val) return true; + if (!new_val) return 0; new_val[len]=0; } else new_val= 0; + return new_val; + } + void global_update_finish(char *new_val) + { if (flags & ALLOCATED) my_free(global_var(char*)); flags|= ALLOCATED; global_var(char*)= new_val; - return false; } - void session_save_default(THD *thd, set_var *var) - { DBUG_ASSERT(FALSE); } + bool global_update(THD *thd, set_var *var) + { + char *new_val= global_update_prepare(thd, var); + global_update_finish(new_val); + return (new_val == 0 && var->save_result.string_value.str != 0); + } + void session_save_default(THD *thd, set_var *var)= 0; void global_save_default(THD *thd, set_var *var) { char *ptr= (char*)(intptr)option.def_value; @@ -536,6 +538,104 @@ public: } }; +class Sys_var_charptr: public Sys_var_charptr_base +{ +public: + Sys_var_charptr(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt, + enum charset_enum is_os_charset_arg, + const char *def_val, PolyLock *lock=0, + enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG, + on_check_function on_check_func=0, + on_update_function on_update_func=0, + const char *substitute=0) : + Sys_var_charptr_base(name_arg, comment, flag_args, off, size, getopt, + is_os_charset_arg, def_val, lock, binlog_status_arg, + on_check_func, on_update_func, substitute) + { + SYSVAR_ASSERT(scope() == GLOBAL); + SYSVAR_ASSERT(size == sizeof(char *)); + } + + bool session_update(THD *thd, set_var *var) + { + DBUG_ASSERT(FALSE); + return true; + } + void session_save_default(THD *thd, set_var *var) + { DBUG_ASSERT(FALSE); } +}; + +#ifndef EMBEDDED_LIBRARY +class Sys_var_sesvartrack: public Sys_var_charptr_base +{ +public: + Sys_var_sesvartrack(const char *name_arg, + const char *comment, + CMD_LINE getopt, + enum charset_enum is_os_charset_arg, + const char *def_val, PolyLock *lock) : + Sys_var_charptr_base(name_arg, comment, + SESSION_VAR(session_track_system_variables), + getopt, is_os_charset_arg, def_val, lock, + VARIABLE_NOT_IN_BINLOG, 0, 0, 0) + {} + bool do_check(THD *thd, set_var *var) + { + if (Sys_var_charptr_base::do_check(thd, var) || + sysvartrack_validate_value(thd, var->save_result.string_value.str, + var->save_result.string_value.length)) + return TRUE; + return FALSE; + } + bool global_update(THD *thd, set_var *var) + { + char *new_val= global_update_prepare(thd, var); + if (new_val) + { + if (sysvartrack_reprint_value(thd, new_val, + var->save_result.string_value.length)) + new_val= 0; + } + global_update_finish(new_val); + return (new_val == 0 && var->save_result.string_value.str != 0); + } + bool session_update(THD *thd, set_var *var) + { + return sysvartrack_update(thd, var); + } + void session_save_default(THD *thd, set_var *var) + { + var->save_result.string_value.str= global_var(char*); + var->save_result.string_value.length= + strlen(var->save_result.string_value.str); + /* parse and feel list with default values */ + if (thd) + { + bool res= + sysvartrack_validate_value(thd, + var->save_result.string_value.str, + var->save_result.string_value.length); + DBUG_ASSERT(res == 0); + } + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { + DBUG_ASSERT(thd != NULL); + size_t len= sysvartrack_value_len(thd); + char *res= (char *)thd->alloc(len + sizeof(char *)); + if (res) + { + char *buf= res + sizeof(char *); + *((char**) res)= buf; + sysvartrack_value_construct(thd, buf, len); + } + return (uchar *)res; + } +}; +#endif //EMBEDDED_LIBRARY + class Sys_var_proxy_user: public sys_var { @@ -1978,7 +2078,47 @@ public: if (var->type == OPT_SESSION && Sys_var_enum::session_update(thd, var)) return TRUE; if (var->type == OPT_DEFAULT || !thd->in_active_multi_stmt_transaction()) + { +#ifndef EMBEDDED_LIBRARY + Transaction_state_tracker *tst= NULL; + + if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) + tst= (Transaction_state_tracker *) + thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER); +#endif //EMBEDDED_LIBRARY + thd->tx_isolation= (enum_tx_isolation) var->save_result.ulonglong_value; + +#ifndef EMBEDDED_LIBRARY + if (var->type == OPT_DEFAULT) + { + enum enum_tx_isol_level l; + switch (thd->tx_isolation) { + case ISO_READ_UNCOMMITTED: + l= TX_ISOL_UNCOMMITTED; + break; + case ISO_READ_COMMITTED: + l= TX_ISOL_COMMITTED; + break; + case ISO_REPEATABLE_READ: + l= TX_ISOL_REPEATABLE; + break; + case ISO_SERIALIZABLE: + l= TX_ISOL_SERIALIZABLE; + break; + default: + DBUG_ASSERT(0); + return TRUE; + } + if (tst) + tst->set_isol_level(thd, l); + } + else if (tst) + { + tst->set_isol_level(thd, TX_ISOL_INHERIT); + } +#endif //EMBEDDED_LIBRARY + } return FALSE; } }; diff --git a/sql/transaction.cc b/sql/transaction.cc index 8b188709ce6..d728ea25b65 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -25,6 +25,43 @@ #include "debug_sync.h" // DEBUG_SYNC #include "sql_acl.h" + +#ifndef EMBEDDED_LIBRARY +/** + Helper: Tell tracker (if any) that transaction ended. +*/ +static void trans_track_end_trx(THD *thd) +{ + if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) + { + ((Transaction_state_tracker *) + thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER))->end_trx(thd); + } +} + + +/** + Helper: transaction ended, SET TRANSACTION one-shot variables + revert to session values. Let the transaction state tracker know. +*/ +void trans_reset_one_shot_chistics(THD *thd) +{ + if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) + { + Transaction_state_tracker *tst= (Transaction_state_tracker *) + thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER); + + tst->set_read_flags(thd, TX_READ_INHERIT); + tst->set_isol_level(thd, TX_ISOL_INHERIT); + } + + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; +} +#else +#define trans_track_end_trx(A) do{}while(0) +#endif //EMBEDDED_LIBRARY + /* Conditions under which the transaction state must not change. */ static bool trans_check(THD *thd) { @@ -125,11 +162,20 @@ static bool xa_trans_force_rollback(THD *thd) bool trans_begin(THD *thd, uint flags) { int res= FALSE; +#ifndef EMBEDDED_LIBRARY + Transaction_state_tracker *tst= NULL; +#endif //EMBEDDED_LIBRARY DBUG_ENTER("trans_begin"); if (trans_check(thd)) DBUG_RETURN(TRUE); +#ifndef EMBEDDED_LIBRARY + if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) + tst= (Transaction_state_tracker *) + thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER); +#endif //EMBEDDED_LIBRARY + thd->locked_tables_list.unlock_locked_tables(thd); DBUG_ASSERT(!thd->locked_tables_mode); @@ -172,7 +218,13 @@ bool trans_begin(THD *thd, uint flags) DBUG_ASSERT(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) && (flags & MYSQL_START_TRANS_OPT_READ_WRITE))); if (flags & MYSQL_START_TRANS_OPT_READ_ONLY) + { thd->tx_read_only= true; +#ifndef EMBEDDED_LIBRARY + if (tst) + tst->set_read_flags(thd, TX_READ_ONLY); +#endif //EMBEDDED_LIBRARY + } else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE) { /* @@ -189,6 +241,14 @@ bool trans_begin(THD *thd, uint flags) DBUG_RETURN(true); } thd->tx_read_only= false; + /* + This flags that tx_read_only was set explicitly, rather than + just from the session's default. + */ +#ifndef EMBEDDED_LIBRARY + if (tst) + tst->set_read_flags(thd, TX_READ_WRITE); +#endif //EMBEDDED_LIBRARY } #ifdef WITH_WSREP @@ -203,9 +263,20 @@ bool trans_begin(THD *thd, uint flags) thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY; DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS")); +#ifndef EMBEDDED_LIBRARY + if (tst) + tst->add_trx_state(thd, TX_EXPLICIT); +#endif //EMBEDDED_LIBRARY + /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */ if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) + { +#ifndef EMBEDDED_LIBRARY + if (tst) + tst->add_trx_state(thd, TX_WITH_SNAPSHOT); +#endif //EMBEDDED_LIBRARY res= ha_start_consistent_snapshot(thd); + } DBUG_RETURN(MY_TEST(res)); } @@ -255,6 +326,8 @@ bool trans_commit(THD *thd) thd->transaction.all.m_unsafe_rollback_flags&= ~THD_TRANS::DID_WAIT; thd->lex->start_transaction_opt= 0; + trans_track_end_trx(thd); + DBUG_RETURN(MY_TEST(res)); } @@ -308,8 +381,9 @@ bool trans_commit_implicit(THD *thd) @@session.completion_type since it's documented to not have any effect on implicit commit. */ - thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; - thd->tx_read_only= thd->variables.tx_read_only; + trans_reset_one_shot_chistics(thd); + + trans_track_end_trx(thd); DBUG_RETURN(res); } @@ -349,6 +423,8 @@ bool trans_rollback(THD *thd) thd->transaction.all.m_unsafe_rollback_flags&= ~THD_TRANS::DID_WAIT; thd->lex->start_transaction_opt= 0; + trans_track_end_trx(thd); + DBUG_RETURN(MY_TEST(res)); } @@ -396,6 +472,8 @@ bool trans_rollback_implicit(THD *thd) /* Rollback should clear transaction_rollback_request flag. */ DBUG_ASSERT(! thd->transaction_rollback_request); + trans_track_end_trx(thd); + DBUG_RETURN(MY_TEST(res)); } @@ -434,8 +512,7 @@ bool trans_commit_stmt(THD *thd) res= ha_commit_trans(thd, FALSE); if (! thd->in_active_multi_stmt_transaction()) { - thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; - thd->tx_read_only= thd->variables.tx_read_only; + trans_reset_one_shot_chistics(thd); if (WSREP_ON) wsrep_post_commit(thd, FALSE); } @@ -487,10 +564,7 @@ bool trans_rollback_stmt(THD *thd) wsrep_register_hton(thd, FALSE); ha_rollback_trans(thd, FALSE); if (! thd->in_active_multi_stmt_transaction()) - { - thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; - thd->tx_read_only= thd->variables.tx_read_only; - } + trans_reset_one_shot_chistics(thd); } (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE)); @@ -912,6 +986,8 @@ bool trans_xa_commit(THD *thd) xid_cache_delete(thd, &thd->transaction.xid_state); thd->transaction.xid_state.xa_state= XA_NOTR; + trans_track_end_trx(thd); + DBUG_RETURN(res); } @@ -968,5 +1044,7 @@ bool trans_xa_rollback(THD *thd) xid_cache_delete(thd, &thd->transaction.xid_state); thd->transaction.xid_state.xa_state= XA_NOTR; + trans_track_end_trx(thd); + DBUG_RETURN(res); } diff --git a/sql/transaction.h b/sql/transaction.h index 54b25f1de2a..040f1a453cd 100644 --- a/sql/transaction.h +++ b/sql/transaction.h @@ -44,4 +44,10 @@ bool trans_xa_prepare(THD *thd); bool trans_xa_commit(THD *thd); bool trans_xa_rollback(THD *thd); +#ifndef EMBEDDED_LIBRARY +void trans_reset_one_shot_chistics(THD *thd); +#else +#define trans_reset_one_shot_chistics(A) do{}while(0) +#endif //EMBEDDED_LIBRARY + #endif /* TRANSACTION_H */