[Ruby][MySQL] Ruby/MySQL 3.0.0 alpha

github に Ruby/MySQL 3.0.0 を置きました。

git にも gem にも慣れてないので試行錯誤でしたが、なんとか置けたようです。

github は gemspec を置いておけば自動的に gem を作ってくれるはずなのですが、罠に嵌まってもがいてました。

GitHub では *.gemspec ファイルのバージョン番号が更新されたときにのみ Gem を生成する。だから *.gemspec を最初に commit & push したときは、Gem が生成されない(バージョン番号が更新されているわけではないから)。まずは *.gemspec ファイルをバージョン 0.0.0 とかで commit & push し、そのあとバージョンを上げて commit & push し直す。

GitHub で gem を自動作成させるときの注意

gemspec のバージョンを変更して git push したらちゃんとできました。

次のようにすればインストールできるはずです。

# sudo gem install tmtm-ruby-mysql --source http://gems.github.com

…と言っておいてなんですが、Ruby/MySQL 3.0.0 はアルファ版なので、ヒトバシラー以外は使ってはいけません。

特徴等

Ruby/MySQL は Ruby から MySQL を使用するためのライブラリです。

  • MySQL/Ruby と異なり Ruby で書かれているのでコンパイルの必要はありません。
  • Ruby ライセンスです。libmysqlclient は使用していないので GPL ライセンスに縛られません。
  • Ruby 1.9 の M17N に対応しています。
  • Ruby/MySQL 0.x とも MySQL/Ruby 2.x とも互換はありません。
  • まともなドキュメントがありません (ぉ
  • MySQL/Ruby 2.x よりも遅いです。たぶん。

使用例

Mysql.connect("mysql://user:passwd@server:3306/dbname") do |my|
  my.query("select col1,col2 from tblname").each do |col1, col2|
    p col1, col2
  end
  my.query("insert into tblname (col1,col2) values (?,?)", 123, "abc")
end

接続

Mysql.new は Mysql オブジェクトを生成するだけでサーバーに接続はしません。Mysql.connect または Mysql#connect を使用してください。

Mysql.connect の引数には上記のようなURI 文字列の他に、URI オブジェクト、Hash 等を使用できます。

# URI文字列
Mysql.connect("mysql://user:passwd@server/dbname")

# URIオブジェクト
Mysql.connect URI.parse("mysql://user:passwd@server/dbname")

# Hash
Mysql.connect(:host=>"server", :user=>"user", :password=>"passwd", :db=>"dbname")

# 以前の形式
Mysql.connect("server", "user", "passwd", "dbname")

Mysql.new または Mysql.connect にブロックを渡すと、ブロックを抜ける時に自動的に接続を切断します。

クエリ

Mysql#query でクエリを発行します。クエリ文字列が "sel" で始まる場合はプリペアドステートメントとして実行します。(3.0.1で廃止)プリペアドステートメントクエリの結果は MySQL の型に応じた Ruby オブジェクトになります。以前とは異なり文字列だけとは限りません。

my.query("select 123,'abc'").fetch # => [123, "abc"]

また Mysql#query に引数が2つ以上ある場合もプリペアドステートメントとして実行します。

my.query("insert into tblname (col1,col2) values (?,?)", 123, "abc")

もちろん、プリペアドステートメントを明示して実行することもできます。

stmt = my.prepare("select ?,?")
stmt.execute 123, "abc"
stmt.fetch  # => [123, "abc"]

プリペアドステートメントを使用したくない場合は Mysql#simple_query を使用します。クエリの結果は文字列になります。(3.0.1で廃止)

my.simple_query("select 123,'abc'").fetch # => ["123", "abc"]

エラー

MySQL/Ruby 2.x では例外クラスはすべて Mysql::Error で、エラー種別を判定したい場合は、rescue した後、Mysql::Error#errno を見る必要がありました。Ruby/MySQL 3.0 ではエラーの種類毎に例外クラスが存在するので、rescue でエラーを振り分けできます。まあ、今までがひどかったのですけどね。

begin
  my.query("....")
rescue Mysql::DupEntry
  ...
rescue Mysql::ParseError
  ...
rescue Mysql::NoSuchTable
  ...
end

Charset

Ruby 1.9 では文字列の encoding/charset の自動変換を行います。

特に charset を指定しない場合は MySQL サーバー接続時にサーバーから得られた charset(mysqld の default-character-set パラメータ)を Mysql#charset に保持します。

charset を指定するには Mysql#charset= を使用します。

my.charset = "utf8"

クライアントからサーバーに送られるクエリやデータの文字列は、自動的に Mysql#charset に変換されます。また、サーバーからクライアントに送られる文字列は Mysql#charset に対応する Ruby のエンコーディング文字列として返されます。変換できない場合はエラーになります。

mysqld の default-character-set パラメータが latin1 の場合(これは mysqld のデフォルト値です)、次のスクリプトを実行すると Encoding::UndefinedConversionError 例外が発生します。

# -*- coding:utf-8 -*-
require "mysql"
Mysql.connect(...) do |my|
  p my.query("select 'あいう'").fetch
end

次のように charset を指定すればエラーになりません。なお、Mysql#charset= に指定できる文字列は Ruby のエンコーディング名ではなく、MySQL の charset 名なので "utf-8" ではなく "utf8" です。

# -*- coding:utf-8 -*-
require "mysql"
Mysql.connect(...) do |my|
  my.charset = "utf8"
  p my.query("select 'あいう'").fetch
end

サーバーから取り出される文字列はデータベース上の charset に関係なく、Mysql#charset に対応するエンコーディングの Ruby 文字列になります。

たとえば次のようなテーブルとレコードがある場合、

mysql> show create table t\G
*************************** 1. row ***************************
       Table: t
Create Table: CREATE TABLE `t` (
  `euc` char(10) CHARACTER SET eucjpms DEFAULT NULL,
  `sjis` char(10) CHARACTER SET cp932 DEFAULT NULL,
  `utf8` char(10) CHARACTER SET utf8 DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1

mysql> select euc,hex(euc),sjis,hex(sjis),utf8,hex(utf8) from t\G
*************************** 1. row ***************************
      euc: あいう
 hex(euc): A4A2A4A4A4A6
     sjis: あいう
hex(sjis): 82A082A282A4
     utf8: あいう
hex(utf8): E38182E38184E38186

それぞれのカラムは、異なる charset の文字列が格納されていますが、Ruby から次のように取り出した場合はすべて UTF-8 文字列になります。

# -*- coding:utf-8 -*-
require "mysql"
Mysql.connect(...) do |my|
  my.charset = "utf8"
  rec = my.query("select * from t").fetch
  rec.each{|c| p c, c.encoding}
end

結果:

"あいう"
#<Encoding:UTF-8>
"あいう"
#<Encoding:UTF-8>
"あいう"
#<Encoding:UTF-8>

なお、BINARY型や BLOB型のデータは charset によらず、そのままのバイト列で取り出せます。Ruby 文字列のエンコードは ASCII-8BIT になります。

おわりに

MySQL/Ruby と比べて遅かったり、非互換があったりしますが、Ruby 1.9 の M17N がちゃんと扱えるのはこれだけなので、Ruby 1.9 な人はこれを使ってみるのもいいと思います。

ただ、上にも書きましたが、まだアルファ版なので、今後非互換な変更が入る可能性もあります。

まあ、フツーの人は Rails とか O/Rマッパー経由で使うだろうから、あまり関係ないでしょうけどね。