ruby-mysql と ruby-mysql2
これはMySQLアドベントカレンダーとRubyアドベントカレンダーの3日目の記事です。
ruby-mysql
誰も使わないだろうけど、ruby-mysql 4.0 をリリースした。
ruby-mysql | RubyGems.org | コミュニティのGemホスティングサービス
ruby-mysql は Ruby で書かれた MySQL 用のクライアントライブラリ。
3.0 に対する大きな変更点
Mysql#query の結果の値オブジェクトのクラス
今まではプリペアドステートメント(Mysql#prepare)ではない通常のクエリ(Mysql#query)の結果の値はすべて String で返していた。
プリペアドステートメントの場合は、MySQL の型に応じたオブジェクトを返していた。
4.0 で通常のクエリでもプリペアドステートメントと同様のオブジェクトで結果を返すようにした。
あと DECIMAL と DATE の型も変更した。
MySQL type | Ruby class |
---|---|
NULL | NilClass |
INT | Integer |
DECIMAL | BigDecimal (3.0 までは String) |
FLOAT, DOUBLE | Float |
DATE | Date (3.0 までは Time) |
DATETIME, TIMESTAMP | Time |
TIME | Float (秒単位) |
YEAR | Integer |
CHAR, VARCHAR | String |
BIT | String |
TEXT, BLOB, JSON | String |
3.0:
pp my.query('select 123,123.45,now(),cast(now() as date)').fetch.map{[_1, _1.class]}
#=> [["123", String],
# ["123.45", String],
# ["2022-11-15 00:17:11", String],
# ["2022-11-15", String]]
4.0:
pp my.query('select 123,123.45,now(),cast(now() as date)').fetch.map{[_1, _1.class]}
#=> [[123, Integer],
# [0.12345e3, BigDecimal],
# [2022-11-15 00:17:17 +0900, Time],
# [#<Date: 2022-11-15 ((2459899j,0s,0n),+0s,2299161j)>, Date]]
今までと同じように String で返すには、Mysql.new
とか Mysql.connect
とか Mysql#query
とか Mysql#each
とか Mysql#fetch
とかに cast: false
を指定する。
Mysql.new(cast: false).connect.query('select 123').fetch #=> ["123"]
Mysql.new.connect(cast: false).query('select 123').fetch #=> ["123"]
Mysql.connect(cast: false).query('select 123').fetch #=> ["123"]
Mysql.connect.query('select 123', cast: false).fetch #=> ["123"]
Mysql.connect.query('select 123').each(cast: false).first #=> ["123"]
Mysql.connect.query('select 123').fetch(cast: false) #=> ["123"]
または、Mysql.default_options
を変更すると、それ以降そのプロセスで生成された Mysql オブジェクトの振る舞いが変更される。
m1 = Mysql.connect
Mysql.default_options[:cast] = false
m2 = Mysql.connect
m1.query('select 123').fetch #=> [123]
m2.query('select 123').fetch #=> ["123"]
Mysql::Result#each が毎回先頭から繰り返す
3.0 までは each は前回の続きから結果を返すが、4.0 では最初から繰り返す。
3.0:
res = my.query('select 1 union select 2 union select 3')
res.entries #=> [["1"], ["2"], ["3"]]
res.entries #=> []
res = my.query('select 1 union select 2 union select 3')
res.each.first #=> ["1"]
res.each.first #=> ["2"]
res.each.first #=> ["3"]
res.each.first #=> nil
4.0:
res = my.query('select 1 union select 2 union select 3')
res.entries #=> [[1], [2], [3]]
res.entries #=> [[1], [2], [3]]
res = my.query('select 1 union select 2 union select 3')
res.each.first #=> [1]
res.each.first #=> [1]
res.each.first #=> [1]
3.0 と同じ振る舞いにしたいことはないと思うけど、もししたい場合は、こんな感じで:
require 'mysql'
class Mysql::Result
def each(**opts)
while (r = fetch(**opts))
yield r
end
end
end
my = Mysql.connect
res = my.query('select 1 union select 2 union select 3')
pp res.entries #=> [[1], [2], [3]]
pp res.entries #=> []
その他
RSpec
テストコードは test-unit で書いてたんだけど、RSpec を使うようにした。慣れてるので。
GitHub から GitLab に移行
https://github.com/tmtm/ruby-mysql から https://gitlab.com/tmtms/ruby-mysql に移行した。なんとなく。
ruby-mysql2
mysql2 は C ライブラリの libmysqlclient を使ってるんだけど、その代わりに ruby-mysql を使うと面白いかと思って、mysql2 をベースに ruby-mysql2 を作ってみた。
mysql2 のテストコードはかなり充実してて、ruby-mysql の開発にも役立った。
テストコードはだいたい通ったので、普通に mysql2 の代わりとして使えると思う。
mysql2 との非互換は、これくらい。
- my.cnf 等を読まない
-
caching_sha2_password
,mysql_native_password
,sha256_password
以外の認証方式はサポートしない - Mysql2::EM がない
試しに ActiveRecord で使ってみようと思ったんだけど、mysql2_adapter.rb 中で gem "mysql2"
と書かれてたので、ダメだった。
Sequel では普通に使えた。
まあでも、特に ruby-mysql2 を使う理由はないな。普通に mysql2 を使えばいいんだし。mysql2 に比べたらかなり遅いし。
また誰の役に立たないものを作ってしまった…。
Discussion
ruby-mysql, Sequelのアダプタとしてかなりお世話になってます!
最近だと、AWS Lambdaで依存ライブラリごとzipパッケージアップロードする方法をとるときに、pure rubyなのでとても手軽に同梱できて、助かってます♪