Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client session tracking #1092

Merged
merged 8 commits into from
Mar 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this example might be wrong? Should it be flags: Mysql2::Client::SESSION_TRACK?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pass all sorts of things to flags 😁 -- https://github.com/brianmario/mysql2/blob/master/lib/mysql2/client.rb#L56L57 -- so this will work. It might be better to encourage the use of the constant for style reasons though? The other examples do.

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(...):
Expand Down
53 changes: 53 additions & 0 deletions ext/mysql2/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -1021,6 +1032,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
*
Expand Down Expand Up @@ -1441,6 +1482,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);
Expand Down Expand Up @@ -1613,6 +1655,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));
Expand Down
45 changes: 44 additions & 1 deletion spec/mysql2/client_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'spec_helper'

RSpec.describe Mysql2::Client do
RSpec.describe Mysql2::Client do # rubocop:disable Metrics/BlockLength
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance to disable/extend this rubocop rule in specs only? rspec is usually bunch of huge blocks by design.

context "using defaults file" do
let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) }

Expand Down Expand Up @@ -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|
Expand Down