Ruby 1.9からDBIを使ってPostgreSQLに接続しようとした.postgresモジュールを改造してコネクトまではできたが,クエリーの発行は失敗に終わった.※なお,その後成功しました.その話はこちらです.このページの話は結局,不毛な試行錯誤ということになります (;´Д`)
Ruby1.9からDBIでPostgreSQL接続...失敗最近,Rubyを始めた(ver 1.9).perlの時にDBIを便利に使っていたので, RubyでもDBIを使ってPostgreSQLに接続しようとした.が,苦難の道のりで結局実現できなかった.
※その後,成功しました.成功編はこちら → Ruby1.9からDBIでPostgreSQLに接続. 1.DBIのインストールから,以下をダウンロードした.
DBIは,普通にインストールする. $ tar zxf dbi-0.4.1.tar.gz $ cd dbi-0.4.1 $ ruby setup.rb config $ ruby setup.rb setup $ su # ruby setup.rb install 2.DBD:PgDBD:Pgだが,後述するようにドライバとしてpostgresモジュールを使うので,頭の部分を少し変更する.dbd-pg-0.3.7/lib/dbd/Pg.rb [変更前] require 'pg' [変更後] require 'postgres' 後はDBIと同じように,インストールする $ ruby setup.rb config $ ruby setup.rb setup $ su # ruby setup.rb install 3.PostgreSQLのドライバこれが難関.探したところ,以下の3種類が見つかった.
今回は,C言語かつ比較的新しい「postgres」を使うことにした. (1) まずGemsではどうかとりあえずGemsで試してみる.# gem install postgres Building native extensions. This could take a while... /usr/local/lib/ruby/site_ruby/1.9.1/rubygems/ext/builder.rb:48: warning: Insecure world writable dir /home/kamoi in PATH, mode 040777 ERROR: Error installing postgres: ERROR: Failed to build gem native extension. /usr/local/bin/ruby extconf.rb install postgres extconf.rb:4:in `<main>': uninitialized constant PLATFORM (NameError) 調べてみたが, によると,このモジュールがRuby 1.9に対応していないという場合のエラーらしい. そこでこのページを参考にして,1.9でも使えるように改造することにした. (2) gemからファイルを取り出す$ gem unpack postgres-0.7.9.2008.01.28.gem (3) extconf.rb を編集する「PLATFORM」の箇所を「RUBY_PLATFORM」に変更する(2箇所).これでextconf.rbは動くようになった$ cd postgres-0.7.9.2008.01.28/ext $ vi extconf.rb 〜 $ ruby extconf.rb --with-pgsql-include=/usr/local/pgsql/include \ --with-pgsql-lib=/usr/local/pgsql/lib $ make (4) postgres.c を編集する上記のページによると,ruby 1.9では配列データ構造体(RArray)の定義が変わったようで,コンパイルエラーはどうもそれが原因. メンバptr,lenを使わずにマクロ(RARRAY_LEN,RARRAY_PTR)を使うように修正すれば,コンパイルエラーは無くなった.$ make # make install $ irb irb(main):001:0> require 'dbi' => true irb(main):002:0> DBI.connect('DBI:Pg:test:localhost', 'postgres', '') DBI::InterfaceError: Could not load driver (undefined local variable or method `e' for DBI:Module) from /usr/local/lib/ruby/site_ruby/1.9.1/dbi.rb:312:in `rescue in load_driver' from /usr/local/lib/ruby/site_ruby/1.9.1/dbi.rb:236:in `load_driver' from /usr/local/lib/ruby/site_ruby/1.9.1/dbi.rb:154:in `_get_full_driver' from /usr/local/lib/ruby/site_ruby/1.9.1/dbi.rb:139:in `connect' from (irb):2 from /usr/local/bin/irb:12:in `<main>' しかしこのエラーメッセージだけでは現象がさっぱりわからないので,Ruby側(DBD:Pg)とC側(postgres.c)の両方に,トレース出力を仕込んで原因を調べた. Ruby側は「p」,C側は「fprintf(stderr, 〜)」で,画面に表示することができた. その結果,C側でPostgreSQLにクエリーを投げたときに,PostgreSQLから以下のエラーを受け取っていることがわかった DBI::OperationalError: ERROR: bind message supplies 1 parameters, but prepared statement "" requires 0 なぜこんなことになるかというと,どうもC側の pgconn_exec(int argc, VALUE *argv, VALUE obj) 関数をRubyから呼び出すときに,呼び出し側と受け取り側で,引数の解釈が合ってないようだ.
呼び出し側は,
しかし,postgres.c側での受け方は, static VALUE pgconn_exec(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { ・・・ rb_scan_args(argc, argv, "1*", &command, ¶ms); ・・・ if (RARRAY_LEN(params) <= 0) { result = PQexec(conn, StringValuePtr(command)); }
何かが違う...この不整合を直すには,rb_scan_args()関数の呼び出し方を, rb_scan_args(argc, argv, "11" , &command, ¶ms);
という風に,pgconn_exec()内の変数に正しく格納される. 他にも実行時エラーが出たので修正して,何とかDBI.connect()は通るようになった.ふぅ 一応ここまでの結果のパッチ(実は役に立たないと判明するのだが) (5) 挫折気をよくして,クエリーを投げるプログラムを走らせてみた. (localhostのtestデータベースに,「partition_manage」というテーブルがあるという前提)test.rb
$ ./test.rb /usr/local/lib/ruby/site_ruby/1.9.1/dbd/pg/exec.rb:19:in `prepare': undefined method `prepare' for #<PGconn:0x85432bc> (NoMethodError) from /usr/local/lib/ruby/site_ruby/1.9.1/dbd/pg/database.rb:314:in `_prepare' from /usr/local/lib/ruby/site_ruby/1.9.1/dbd/pg/statement.rb:135:in `internal_prepare' from /usr/local/lib/ruby/site_ruby/1.9.1/dbd/pg/statement.rb:47:in `execute' from /usr/local/lib/ruby/site_ruby/1.9.1/dbi/base_classes/database.rb:96:in `execute' from /usr/local/lib/ruby/site_ruby/1.9.1/dbi/base_classes/database.rb:114:in `do' from /usr/local/lib/ruby/site_ruby/1.9.1/dbi/handles/database.rb:102:in `do' from ./test.rb:8:in `<main>' これは,クエリーをexecuteしたときに,DBD:Pgの内部(exec.rb)でpostgresドライバのprepareというメソッドを実行しようとして, メソッドがなかったというエラーだ. 確かに,postgresドライバのソース(postgres.c)を見ると,prepareというメソッドは無い. (というか,rb_define_method()していない) 糸冬 了 ! orz え〜,今後の策としては,
のどちらかだろうか.どっちにしても苦労するなぁ...トホホ ※その後,ruby-pgという有望なのが見つかった.これには何とprepareもexec_preparedも含まれているので,これを1.9でもコンパイルできるように修正すれば,DBIでPostgreSQLに接続するという目的を達成できるかも知れない. |