Ruby/DBIの代わりにRDBIを使う
概要
今までRuby 1.8がメインであったが、いい加減Ruby 2.0を使おうと思い立った。
Rubyからデータベースに接続するライブラリは様々だが今まで(ver1.8まで)はdbi, dbd-pg, pgを主に使用していた。
- Ruby/DBI https://rubygems.org/gems/dbi
- DBD::Pg https://rubygems.org/gems/dbd-pg
- pg https://rubygems.org/gems/pg
しかし開発者(erikh)によるとdbiはRuby 1.8までしかサポートされていないようである。
https://github.com/erikh/ruby-dbi/issues/5
上記では、同開発者がRuby/DBIの"Version2"として作成したRDBIの使用を推奨している。
ということでRDBIとそのPostgreSQL用ドライバーであるrdbi-driver-postgresqlを調査する
インストール
gemだけ
$ gem install pg
$ gem install rdbi
$ gem install rdbi-driver-postgresql
実行
#-*- coding:utf-8 -*-
require 'rdbi'
require 'rdbi-driver-postgresql'
dbh = RDBI.connect(:PostgreSQL, :dbname=>"test", :port=>5432, :user=>"user", :password=>"pass")
dbh.execute("SELECT NOW()").fetch(:first) #=> ["2014-11-28 18:13:27.232+09"]
dbh.disconnect
Ruby/DBIでやってたことをRDBIでやるには
Ruby/DBIのコードと見比べると、全く同じ使い方ではなさそうだ。
簡単に比較してみる
requireする
require 'dbi'
#require 'dbd-pg' #不要
require 'rdbi'
require 'rdbi-driver-postgresql' #必要
RDBIはDriverを自動読み込みしないので
DBに接続する
dbh = DBI.connect("dbi:Pg:dbname", "user", "pass")
# or
#dbh = DBI.connect("dbi:Pg:database=dbname;port=5432;host=localhost", "user", "pass")
dbh = RDBI.connect(:PostgreSQL, :dbname=>"dbname", :user=>"user", :password=>"pass")
# or
#dbh = RDBI.connect(RDBI::Driver::PostgreSQL, :dbname=>"dbname", :user=>"user", :password=>"pass")
connectの第一引数にDriverのSymbolかClassを指定する
SELECTする
#1行
dbh.select_one("SELECT * FROM table_name") #=> [1, "aaa"]
#全行
dbh.select_all("SELECT * FROM table_name") #=> [[1, "aaa"], [2, "bbb"], [3, "ccc"]]
#1行
dbh.execute("SELECT * FROM table_name").fetch(:first) #=> [1, "aaa"]
#全行
dbh.execute("SELECT * FROM table_name").fetch(:all) #=> [[1, "aaa"], [2, "bbb"], [3, "ccc"]]
トランザクション
dbh["AutoCommit"] = false
begin
# トランザクション中の処理
dbh.commit
rescue
# 失敗
dbh.rollback
end
dbh.execute("BEGIN").fetch
begin
# トランザクション中の処理
dbh.execute("COMMIT")
rescue
# 失敗
dbh.execute("ROLLBACK")
end
transactionメソッドを使うと何故かエラーが出た
rdbi-driver-postgresql(0.9.2) のバグ
調査中、エラーが出たのでの原因を探った。
結論から言うとrdbi-driver-postgresqlが原因のバグを2つほどみつけた。
カラム名が重複した場合のfetch(:first)の結果がおかしい
PostgreSQLではSELECT文にテーブル指定は必須ではない。
以下のSQLをpsqlで実行した場合
SELECT 3,2,1,NOW();
?column? | ?column? | ?column? | now
----------+----------+----------+---------------------------
3 | 2 | 1 | 2014-12-19 14:43:24.77+09
(1 row)
のように、カラム名に ?column?
が複数回現れる。
こういったSQLは rdbi-driver-postgresql ではうまくいかない。
dbh.execute("SELECT 3,2,1,NOW()").fetch(:first)
#=> [1, "2014-12-19 14:43:24.77+09"]
とりあえず以下のように書けば回避は可能
dbh.execute("SELECT 3,2,1,NOW()").fetch(:all)[0]
#=> [3, 2, 1, "2014-12-19 14:43:24.77+09"]
連続してSQL実行するとエラーが出る
PreparedStatementには名前を指定出来るのだがそれに Time.now.to_f
を使用しており、連続してexecuteすると名前が重複してエラーが発生する。
10.times do
dbh.execute("SELECT 1")
end
これを回避するには、いちいち実行前にsleepすればいいだろうが
ばからしいのでバグってる箇所を修正した方が良い。
--- lib/ruby/gems/2.0.0/gems/rdbi-driver-postgresql-0.9.2/lib/rdbi/driver/postgresql.rb.original Fri 11 28 10:32:52 2014
+++ lib/ruby/gems/2.0.0/gems/rdbi-driver-postgresql-0.9.2/lib/rdbi/driver/postgresql.rb Fri 12 12 10:11:51 2014
@@ -175,9 +175,9 @@ class RDBI::Driver::PostgreSQL < RDBI::Driver
end
def next_row
- val = @handle[@index].values
+ val = self[@index]
@index += 1
- fix_dates(val)
+ val
end
def result_count
@@ -189,11 +189,11 @@ class RDBI::Driver::PostgreSQL < RDBI::Driver
end
def first
- fix_dates(@handle[0].values)
+ self[0]
end
def last
- fix_dates(@handle[-1].values)
+ self[result_count-1]
end
def rest
@@ -206,7 +206,7 @@ class RDBI::Driver::PostgreSQL < RDBI::Driver
end
def [](index)
- fix_dates(@handle[index].values)
+ fix_dates(fetch_range(index, index)[0])
end
def last_row?
@@ -281,7 +281,7 @@ class RDBI::Driver::PostgreSQL < RDBI::Driver
def initialize( query, dbh )
super( query, dbh )
- @stmt_name = ('stmt_' + Time.now.to_f.to_s).tr('.', '_')
+ @stmt_name = ('stmt_' + self.object_id.to_s + Time.now.to_f.to_s).tr('.', '_')
ep = Epoxy.new( query )
@index_map = ep.indexed_binds