diff --git a/README.md b/README.md index 69563c4df..4094062f6 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,23 @@ statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND locat result = statement.execute(1, "CA", :as => :array) ``` +Session Tracking information can be accessed with + +```ruby +c = Mysql2::Client.new( + host: "127.0.0.1", + username: "root", + flags: "SESSION_TRACK", + init_command: "SET @@SESSION.session_track_schema=ON" +) +c.query("INSERT INTO test VALUES (1)") +session_track_type = Mysql2::Client::SESSION_TRACK_SCHEMA +session_track_data = c.session_track(session_track_type) +``` + +The types of session track types can be found at +[https://dev.mysql.com/doc/refman/5.7/en/mysql-session-track-get-first.html](https://dev.mysql.com/doc/refman/5.7/en/mysql-session-track-get-first.html) + ## Connection options You may set the following connection options in Mysql2::Client.new(...): diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c index 3ba393f52..8f3d403b7 100644 --- a/ext/mysql2/client.c +++ b/ext/mysql2/client.c @@ -57,6 +57,17 @@ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION #endif +/* + * mariadb-connector-c defines CLIENT_SESSION_TRACKING and SESSION_TRACK_TRANSACTION_TYPE + * while mysql-connector-c defines CLIENT_SESSION_TRACK and SESSION_TRACK_TRANSACTION_STATE + * This is a hack to take care of both clients. + */ +#if defined(CLIENT_SESSION_TRACK) +#elif defined(CLIENT_SESSION_TRACKING) + #define CLIENT_SESSION_TRACK CLIENT_SESSION_TRACKING + #define SESSION_TRACK_TRANSACTION_STATE SESSION_TRACK_TRANSACTION_TYPE +#endif + /* * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10. */ @@ -1022,6 +1033,36 @@ static VALUE rb_mysql_client_last_id(VALUE self) { return ULL2NUM(mysql_insert_id(wrapper->client)); } +/* call-seq: + * client.session_track + * + * Returns information about changes to the session state on the server. + */ +static VALUE rb_mysql_client_session_track(VALUE self, VALUE type) { +#ifdef CLIENT_SESSION_TRACK + const char *data; + size_t length; + my_ulonglong retVal; + GET_CLIENT(self); + + REQUIRE_CONNECTED(wrapper); + retVal = mysql_session_track_get_first(wrapper->client, NUM2INT(type), &data, &length); + if (retVal != 0) { + return Qnil; + } + VALUE rbAry = rb_ary_new(); + VALUE rbFirst = rb_str_new(data, length); + rb_ary_push(rbAry, rbFirst); + while(mysql_session_track_get_next(wrapper->client, NUM2INT(type), &data, &length) == 0) { + VALUE rbNext = rb_str_new(data, length); + rb_ary_push(rbAry, rbNext); + } + return rbAry; +#else + return Qnil; +#endif +} + /* call-seq: * client.affected_rows * @@ -1442,6 +1483,7 @@ void init_mysql2_client() { rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0); rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0); rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0); + rb_define_method(cMysql2Client, "session_track", rb_mysql_client_session_track, 1); rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1); rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1); @@ -1614,6 +1656,17 @@ void init_mysql2_client() { INT2NUM(0)); #endif +#ifdef CLIENT_SESSION_TRACK + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK"), INT2NUM(CLIENT_SESSION_TRACK)); + /* From mysql_com.h -- but stable from at least 5.7.4 through 8.0.20 */ + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_SYSTEM_VARIABLES"), INT2NUM(SESSION_TRACK_SYSTEM_VARIABLES)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_SCHEMA"), INT2NUM(SESSION_TRACK_SCHEMA)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_STATE_CHANGE"), INT2NUM(SESSION_TRACK_STATE_CHANGE)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_GTIDS"), INT2NUM(SESSION_TRACK_GTIDS)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_TRANSACTION_CHARACTERISTICS"), INT2NUM(SESSION_TRACK_TRANSACTION_CHARACTERISTICS)); + rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_TRANSACTION_STATE"), INT2NUM(SESSION_TRACK_TRANSACTION_STATE)); +#endif + #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED)); rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED)); diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb index 5a3b8a242..0bdd65c9f 100644 --- a/spec/mysql2/client_spec.rb +++ b/spec/mysql2/client_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -RSpec.describe Mysql2::Client do +RSpec.describe Mysql2::Client do # rubocop:disable Metrics/BlockLength context "using defaults file" do let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) } @@ -1026,6 +1026,49 @@ def run_gc expect(@client).to respond_to(:ping) end + context "session_track" do + before(:each) do + unless Mysql2::Client.const_defined?(:SESSION_TRACK) + skip('Server versions must be MySQL 5.7 later.') + end + @client.query("SET @@SESSION.session_track_system_variables='*';") + end + + it "returns changes system variables for SESSION_TRACK_SYSTEM_VARIABLES" do + @client.query("SET @@SESSION.session_track_state_change=ON;") + res = @client.session_track(Mysql2::Client::SESSION_TRACK_SYSTEM_VARIABLES) + expect(res).to eq(%w[session_track_state_change ON]) + end + + it "returns database name for SESSION_TRACK_SCHEMA" do + @client.query("USE information_schema") + res = @client.session_track(Mysql2::Client::SESSION_TRACK_SCHEMA) + expect(res).to eq(["information_schema"]) + end + + it "returns multiple session track type values when available" do + @client.query("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS'") + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_STATE) + expect(res).to eq(["________"]) + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_CHARACTERISTICS) + expect(res).to eq([""]) + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_STATE_CHANGE) + expect(res).to be_nil + + res = @client.session_track(Mysql2::Client::SESSION_TRACK_SYSTEM_VARIABLES) + expect(res).to eq(%w[session_track_transaction_info CHARACTERISTICS]) + end + + it "returns empty array if session track type not found" do + @client.query("SET @@SESSION.session_track_state_change=ON;") + res = @client.session_track(Mysql2::Client::SESSION_TRACK_TRANSACTION_CHARACTERISTICS) + expect(res).to be_nil + end + end + context "select_db" do before(:each) do 2.times do |i|