JdbcAdapterの怪

jruby-1.1RC1 + rails2.0.2でのこと。rake testをしてみたらフィクスチャのロードで以下のようなエラーが出ました。あ、ちなみにDBはHSQLDBね。activerecord-jdbchsqldb-adapter (0.7.1)使ってます。

  1) Error:
test_should_allow_signup(AccountControllerTest):
ActiveRecord::StatementInvalid: ActiveRecord::ActiveRecordError: This function is not supported: INSERT INTO users (crypted_password, login, created_at, email, id, salt, updated_at
) VALUES ('00742970dc9e6319f8019fd54864d3ea740f04b1', 'aaron', '2008-01-29 15:07:05', 'aaron@example.com', 2, '7e3041ebc2fc05a40c60028e2c4901a81035d3cd', '2008-01-30 15:07:05')
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract_adapter.rb:153:in `log'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-jdbc-adapter-0.7.1/lib/active_record/connection_adapters/jdbc_adapter.rb:503:in `execute'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-jdbc-adapter-0.7.1/lib/active_record/connection_adapters/jdbc_adapter.rb:503:in `insert_fixture'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:631:in `insert_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/reflection.rb:55:in `each'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:568:in `insert_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/reflection.rb:55:in `create_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:516:in `each'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:516:in `create_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:516:in `transaction'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:514:in `create_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:516:in `disable_referential_integrity'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:505:in `create_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:516:in `silence'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:504:in `create_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:516:in `load_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:934:in `setup_with_fixtures'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:978:in `full_setup'
    C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/fixtures.rb:977:in `run'

executeメソッドで例外?This function is not supported: で出てるのは普通のINSERT文。おかしいと思って、script/consoleで確認。User.createは問題ない。しかし以下の怪奇現象が!

?> c = ActiveRecord::Base.connection
=> #<ActiveRecord::ConnectionAdapters::JdbcAdapter:0x1901572 ・・・>
>> c.insert("INSERT INTO users (crypted_password, login, created_at, email, id, salt, updated_at) VALUES ('00742970dc9e6319f8019fd54864d3ea740f04b1', 'aaron', '2008-01-29 15:07:06'
, 'aaron@example.com', 2, '7e3041ebc2fc05a40c60028e2c4901a81035d3cd', '2008-01-30 15:07:06')")
=> 2
>> c.execute("INSERT INTO users (crypted_password, login, created_at, email, id, salt, updated_at) VALUES ('00742970dc9e6319f8019fd54864d3ea740f04b1', 'aaron', '2008-01-29 15:07:06
', 'aaron@example.com', 2, '7e3041ebc2fc05a40c60028e2c4901a81035d3cd', '2008-01-30 15:07:06')")
ActiveRecord::StatementInvalid: ActiveRecord::ActiveRecordError: This function is not supported: INSERT INTO users (crypted_password, login, created_at, email, id, salt, updated_at
) VALUES ('00742970dc9e6319f8019fd54864d3ea740f04b1', 'aaron', '2008-01-29 15:07:06', 'aaron@example.com', 2, '7e3041ebc2fc05a40c60028e2c4901a81035d3cd', '2008-01-30 15:07:06')
        from C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract_adapter.rb:150:in `log'
        from C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract_adapter.rb:132:in `execute'
        from C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-jdbc-adapter-0.7.1/lib/active_record/connection_adapters/jdbc_adapter.rb:503:in `signal_status'
>>

なんでActiveRecord::Base.connectionに対してinsertはOKでexecuteはだめなのさ!insertは結局execute呼んでるじゃん!

      def execute(sql, name = nil)
        log(sql, name) do
          _execute(sql,name)
        end
      end
      
      # we need to do it this way, to allow Rails stupid tests to always work
      # even if we define a new execute method. Instead of mixing in a new
      # execute, an _execute should be mixed in.
      def _execute(sql, name = nil)
        if JdbcConnection::select?(sql)
          @connection.execute_query(sql)
        elsif JdbcConnection::insert?(sql)
          @connection.execute_insert(sql)
        else
          @connection.execute_update(sql)
        end
      end

      def update(sql, name = nil) #:nodoc:
        execute(sql, name)
      end

      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
        id = execute(sql, name = nil)
        id_value || id
      end

むきー!_executeの動きが見たいので、以下のように標準出力に出してみる。

module ActiveRecord::ConnectionAdapters
  class JdbcAdapter
      def _execute(sql, name = nil)
        puts "#{self.class} _execute #0"
        if JdbcConnection::select?(sql)
          puts "#{self.class} _execute #1"
          result = @connection.execute_query(sql)
          puts "#{self.class} _execute #2"
          result
        elsif JdbcConnection::insert?(sql)
          puts "#{self.class} _execute #3"
          result = @connection.execute_insert(sql)
          puts "#{self.class} _execute #4"
          result
        else
          puts "#{self.class} _execute #5"
          result = @connection.execute_update(sql)
          puts "#{self.class} _execute #6"
          result
        end
      end
  end
end

で、出た答えがこちら。

>> c.insert("INSERT INTO users (crypted_password, login, created_at, email, id, salt, updated_at) VALUES ('00742970dc9e6319f8019fd54864d3ea740f04b1', 'aaron', '2008-01-29 15:07:06'
, 'aaron@example.com', 2, '7e3041ebc2fc05a40c60028e2c4901a81035d3cd', '2008-01-30 15:07:06')")
ActiveRecord::ConnectionAdapters::JdbcAdapter _execute #0
ActiveRecord::ConnectionAdapters::JdbcAdapter _execute #1
ActiveRecord::ConnectionAdapters::JdbcAdapter _execute #2
=> 2

>> c.execute("INSERT INTO users (crypted_password, login, created_at, email, id, salt, updated_at) VALUES ('00742970dc9e6319f8019fd54864d3ea740f04b1', 'aaron', '2008-01-29 15:07:06
', 'aaron@example.com', 2, '7e3041ebc2fc05a40c60028e2c4901a81035d3cd', '2008-01-30 15:07:06')")
ActiveRecord::ConnectionAdapters::JdbcAdapter _execute #0
ActiveRecord::ConnectionAdapters::JdbcAdapter _execute #3
ActiveRecord::StatementInvalid: ActiveRecord::ActiveRecordError: This function is not supported: INSERT INTO users (crypted_password, login, created_at, email, id, salt, updated_at
) VALUES ('00742970dc9e6319f8019fd54864d3ea740f04b1', 'aaron', '2008-01-29 15:07:06', 'aaron@example.com', 2, '7e3041ebc2fc05a40c60028e2c4901a81035d3cd', '2008-01-30 15:07:06')
        from C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract_adapter.rb:150:in `log'
        from C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract_adapter.rb:132:in `execute'
        from C:/dev/jruby/jruby-1.1RC1/lib/ruby/gems/1.8/gems/activerecord-jdbc-adapter-0.7.1/lib/active_record/connection_adapters/jdbc_adapter.rb:503:in `signal_status'
>>

JdbcConnection::select?(sql)でINSERT文がtrueと判断されている!マジで?っていうか、insertメソッド経由だと JdbcConnection::select?(sql) => true で、executeメソッドからだとJdbcConnection::select?(sql) => falseってこと?ありえなくない?


で、諸悪の根源はselect?メソッドだと思ってgrepしてみたらJdbcAdapterInternalService.javaで宣言してるのね。文字列中にSELECTが含まれているかどうかを判断しようとしているらしいコード(#173〜)を見てみたら何かすごいことしてる・・・。Java側からRubyのオブジェクトに触るのって大変なのね、まだRC1だしバグもあるよね、と及び腰になってしまいました。っていうか、Javaの方もおかしくないと思うんだけどなー。