15
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ruby/DBIの代わりにRDBIを使う

Last updated at Posted at 2014-11-28

Ruby/DBIの代わりにRDBIを使う

概要

今までRuby 1.8がメインであったが、いい加減Ruby 2.0を使おうと思い立った。
Rubyからデータベースに接続するライブラリは様々だが今まで(ver1.8まで)はdbi, dbd-pg, 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

実行

rdbi-sample.rb
#-*- 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する

dbi-require.rb
require 'dbi'
#require 'dbd-pg' #不要
rdbi-require.rb
require 'rdbi'
require 'rdbi-driver-postgresql' #必要

RDBIはDriverを自動読み込みしないので

DBに接続する

dbi-connect.rb
dbh = DBI.connect("dbi:Pg:dbname", "user", "pass")
# or
#dbh = DBI.connect("dbi:Pg:database=dbname;port=5432;host=localhost", "user", "pass")
rdbi-connect.rb
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する

dbi-select.rb
#1行
dbh.select_one("SELECT * FROM table_name") #=> [1, "aaa"]
#全行
dbh.select_all("SELECT * FROM table_name") #=> [[1, "aaa"], [2, "bbb"], [3, "ccc"]]
rdbi-select.rb
#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"]]

トランザクション

dbi-transaction.rb
dbh["AutoCommit"] = false
begin
  # トランザクション中の処理
  dbh.commit
rescue
  # 失敗
  dbh.rollback
end
rdbi-transaction.rb
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
15
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?