MySQL 5.7.12 から追加された X Protocol は Protobuf というのを使ってるらしいです。 Protobuf というのをそこで初めて知ったので、とりあえず Ruby から Protobuf を利用する方法を調べてみました。
Protobuf はデータ構造をバイト列にエンコードしたり、その逆にバイト列をデータ構造にデコードしたりするライブラリのようです。
Ubuntu で protobuf を使うには、protobuf-compiler パッケージをインストールします。
% sudo apt-get install protobuf-compiler
Ruby から Protobuf を使うには、protobuf gem をインストールします。
% gem install protobuf
データ構造は .proto という拡張子のファイルで定義するようです。
MySQL 5.7.12 では rapid/plugin/x/protocol ディレクトリに置かれていました。
% cd mysql-5.7.12/rapid/plugin/x/protocol % ls mysqlx.proto mysqlx_expect.proto mysqlx_session.proto mysqlx_connection.proto mysqlx_expr.proto mysqlx_sql.proto mysqlx_crud.proto mysqlx_notice.proto mysqlx_datatypes.proto mysqlx_resultset.proto
Ruby 用にコンパイル(?)します。
% mkdir /tmp/x % protoc -I . --ruby_out /tmp/x mysqlx.proto [libprotobuf WARNING google/protobuf/descriptor.cc:5411] Warning: Unused import: "mysqlx.proto" imports "mysqlx_resultset.proto" which is not used. [libprotobuf WARNING google/protobuf/descriptor.cc:5411] Warning: Unused import: "mysqlx.proto" imports "mysqlx_session.proto" which is not used. [libprotobuf WARNING google/protobuf/descriptor.cc:5411] Warning: Unused import: "mysqlx.proto" imports "mysqlx_sql.proto" which is not used. [libprotobuf WARNING google/protobuf/descriptor.cc:5411] Warning: Unused import: "mysqlx.proto" imports "mysqlx_connection.proto" which is not used. [libprotobuf WARNING google/protobuf/descriptor.cc:5411] Warning: Unused import: "mysqlx.proto" imports "mysqlx_expect.proto" which is not used. [libprotobuf WARNING google/protobuf/descriptor.cc:5411] Warning: Unused import: "mysqlx.proto" imports "mysqlx_crud.proto" which is not used. [libprotobuf WARNING google/protobuf/descriptor.cc:5411] Warning: Unused import: "mysqlx.proto" imports "mysqlx_notice.proto" which is not used. Suppress tag warning output with PB_NO_TAG_WARNINGS=1. [WARN] .Mysqlx.Datatypes.Scalar object should have 9 tags (1..9), but found 8 tags. [WARN] .ColumnMetaData.FieldType object should have 18 tags (1..18), but found 11 tags. [WARN] .Mysqlx.Crud.Find object should have 10 tags (2..11), but found 9 tags. [WARN] .SessionStateChanged.Parameter object should have 11 tags (1..11), but found 10 tags. [WARN] .ClientMessages.Type object should have 25 tags (1..25), but found 14 tags. [WARN] .ServerMessages.Type object should have 19 tags (0..18), but found 13 tags.
いくつか Warning が出てますが、よくわからないので無視します。
/tmp/x に .proto に対応する .pb.rb ファイルが出来ました。
% cd /tmp/x % ls mysqlx.pb.rb mysqlx_expect.pb.rb mysqlx_session.pb.rb mysqlx_connection.pb.rb mysqlx_expr.pb.rb mysqlx_sql.pb.rb mysqlx_crud.pb.rb mysqlx_notice.pb.rb mysqlx_datatypes.pb.rb mysqlx_resultset.pb.rb
mysql.x.pb.rb 中にある Mysqlx::Error で試してみます。
module Mysqlx ... class Error < ::Protobuf::Message optional ::Mysqlx::Error::Severity, :severity, 1, :default => ::Mysqlx::Error::Severity::ERROR required :uint32, :code, 2 required :string, :sql_state, 4 required :string, :msg, 3 end end
require "mysqlx.pb" e1 = Mysqlx::Error.new(code: 123, sql_state: "XXXXX", msg: "hoge") s = e.encode # => "\x10{\x1A\x04hoge\"\x05XXXXX" e2 = Mysqlx::Error.decode(s) e2.severity # => #<Protobuf::Enum(Mysqlx::Error::Severity)::ERROR=0> e2.code # => 123 e2.sql_state # => "XXXXX" e2.msg # => "hoge"
Mysqlx::Error オブジェクトがバイト列にエンコード(シリアライズ)されて、バイト列からオブジェクトにデコード(デシリアライズ)されたことがわかります。
Mysqlx::Error は severity が optional で、その他の code, sql_state, msg が required とされています。
required メンバーを指定せずにシリアライズするとエラーになります。
e = Mysqlx::Error.new(code: 123) e.encode # => Required field Mysqlx::Error#msg does not have a value. (Protobuf::SerializationError)
また、定義と異なる型を設定しようとしてもエラーになります。
e = Mysqlx::Error.new e.code = 123456789 # => Ok e.code = 12345678901 # => Unacceptable value 12345678901 for field code of type Protobuf::Field::Uint32Field (TypeError)
Mysqlx::Error クラスは Protobuf::Message の継承クラスですが、Protobuf::Message は上記のように型チェックのある構造体のように使えます。
プロトコルのためのデータ構造なので、厳密に型をチェックしているのですね。
とりあえずここまで。