読者です 読者をやめる 読者になる 読者になる

Sequel + ojdbc1.4 で TIMESTAMP 変換エラー

はじめに

Sequel 3.48.0 で ojdbc14.jar (10.2.0.5) を使って TIMESTAMP 型のカラムを含むテーブルを検索したところ下記のようなエラーが発生しました。 (JRuby 1.7.4 で実行)

ただし、ojdbc5.jar・ojdbc6.jar (11.2.0.3) ではこのようなエラーは発生しません。

Sequel::InvalidValue: ArgumentError:
    no time information in "oracle.sql.TIMESTAMP@4500a0bc"

実行したのは以下のようなスクリプトです。

require 'rubygems'
require 'sequel'
require_relative 'lib/ojdbc14.jar'

DB = Sequel.connect('jdbc:oracle:thin:user1/pass1@localhost:1521/XE')

order = DB[:sample_order]

ds = order.where { |o| o.value > 200 }

ds.all.each do |r|
    p r
end

これは下記のようなテーブルを SELECT * FROM "SAMPLE_ORDER" WHERE ("VALUE" > 200) で検索しているだけです。

create table sample_order (
    order_no varchar(10) not null,
    value number(10,0) not null,
    create_date timestamp default sysdate not null,
    primary key (order_no)
)

原因

Sequel の実装が下記のようになっている事と、ojdbc14.jar における oracle.sql.TIMESTAMP クラスの toString メソッドが "oracle.sql.TIMESTAMP@4500a0bc" のような文字列を返す事が原因です。

lib/sequel/adapters/jdbc/oracle.rb (Sequel のソース)
def convert_type_oracle_timestamp(v)
    db.to_application_timestamp(v.to_string)
end

試しに oracle.sql.TIMESTAMP クラスの toString メソッド (JRuby 上では Java::OracleSql::TIMESTAMP の to_string) 等の結果を出力してみると下記のようになりました。

oracle_timestamp_string.rb
require_relative 'lib/ojdbc14.jar'
#require_relative 'lib/ojdbc5.jar'
#require_relative 'lib/ojdbc6.jar'

date = Java::OracleSql::TIMESTAMP.new('2013-06-07 13:20:30')

puts date.to_string
puts date.to_jdbc.to_string
puts date.timestamp_value.to_string
puts date.string_value
実行結果1 (ojdbc14.jar の場合)
> jruby oracle_timestamp_string.rb
oracle.sql.TIMESTAMP@c3e122
2013-06-07 13:20:30.0
2013-06-07 13:20:30.0
2013-6-7 13.20.30.0
実行結果2 (ojdbc5.jar、ojdbc6.jar の場合)
> jruby oracle_timestamp_string.rb
2013-06-07 13:20:30.0
2013-06-07 13:20:30.0
2013-06-07 13:20:30.0
2013-06-07 13:20:30.0

という事で、ojdbc5.jar や ojdbc6.jar を使うようにした方が良さそうです。

回避方法

とりあえず、ojdbc5.jar や ojdbc6.jar を使うのが抜本的な対策ですが。 ojdbc14.jar を使わなければならない場合は convert_type_oracle_timestamp をオープンクラスで変更すればよいと思います。(oracle.sql.TIMESTAMP の toString を変更するのもあり)

search_order.rb
require 'rubygems'
require 'sequel'
# convert_type_oracle_timestamp を変更するため下記 2行の require が必要
require 'sequel/adapters/jdbc'
require 'sequel/adapters/jdbc/oracle'
# Oracle JDBC ドライバーの require
require_relative 'lib/ojdbc14.jar'

class Sequel::JDBC::Oracle::Dataset
    # convert_type_oracle_timestamp の変更
    def convert_type_oracle_timestamp(v)
        db.to_application_timestamp(v.to_jdbc.to_string)
        # 以下でも可
        # db.to_application_timestamp(v.timestamp_value.to_string)
    end
end

DB = Sequel.connect('jdbc:oracle:thin:user1/pass1@localhost:1521/XE')

order = DB[:sample_order]

ds = order.where { |o| o.value > 200 }

ds.all.each do |r|
    p r
end

実行結果は以下のようになり、正常に処理できている事を確認できました。

実行結果
> bundle exec jruby search_order.rb
{:order_no=>"A2", :value=>300, :create_date=>2013-06-07 16:42:00 +0900}
{:order_no=>"A3", :value=>600, :create_date=>2013-06-07 16:45:00 +0900}

今回使ったソースは http://github.com/fits/try_samples/tree/master/blog/20130623/