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

# 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
```


## 実行

```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で実行した場合

```sql
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 ではうまくいかない。

```rb
dbh.execute("SELECT 3,2,1,NOW()").fetch(:first)
#=> [1, "2014-12-19 14:43:24.77+09"]
```

とりあえず以下のように書けば回避は可能

```rb
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すると名前が重複してエラーが発生する。

```rb
10.times do
  dbh.execute("SELECT 1")
end
```

これを回避するには、いちいち実行前にsleepすればいいだろうが
ばからしいのでバグってる箇所を修正した方が良い。

```diff
--- 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
```