[ruby-oci8-commit] [365] trunk/ruby-oci8: * ext/oci8/ocidatetime.c: Add workarounds for Oracle 9.2.0.1.
nobody at rubyforge.org
nobody at rubyforge.org
Sun Oct 18 09:46:49 EDT 2009
Revision: 365
Author: kubo
Date: 2009-10-18 09:46:48 -0400 (Sun, 18 Oct 2009)
Log Message:
-----------
* ext/oci8/ocidatetime.c: Add workarounds for Oracle 9.2.0.1.
* lib/oci8/datetime.rb: Change the unit of OCI8::BindType::IntervalDS
from day to second. Add OCI8::BindType::IntervalDS.unit and
OCI8::BindType::IntervalDS.unit.
* test/test_datetime.rb: Fix tests for the above changes.
Modified Paths:
--------------
trunk/ruby-oci8/ChangeLog
trunk/ruby-oci8/ext/oci8/ocidatetime.c
trunk/ruby-oci8/lib/oci8/datetime.rb
trunk/ruby-oci8/test/test_datetime.rb
Modified: trunk/ruby-oci8/ChangeLog
===================================================================
--- trunk/ruby-oci8/ChangeLog 2009-10-17 14:23:12 UTC (rev 364)
+++ trunk/ruby-oci8/ChangeLog 2009-10-18 13:46:48 UTC (rev 365)
@@ -1,3 +1,10 @@
+2009-10-18 KUBO Takehiro <kubo at jiubao.org>
+ * ext/oci8/ocidatetime.c: Add workarounds for Oracle 9.2.0.1.
+ * lib/oci8/datetime.rb: Change the unit of OCI8::BindType::IntervalDS
+ from day to second. Add OCI8::BindType::IntervalDS.unit and
+ OCI8::BindType::IntervalDS.unit.
+ * test/test_datetime.rb: Fix tests for the above changes.
+
2009-10-17 KUBO Takehiro <kubo at jiubao.org>
* ext/oci8/oci8.c: Add a workaround for Oracle 9.2.0.1 when clearing
a client identifier.
Modified: trunk/ruby-oci8/ext/oci8/ocidatetime.c
===================================================================
--- trunk/ruby-oci8/ext/oci8/ocidatetime.c 2009-10-17 14:23:12 UTC (rev 364)
+++ trunk/ruby-oci8/ext/oci8/ocidatetime.c 2009-10-18 13:46:48 UTC (rev 365)
@@ -320,8 +320,26 @@
}
year = NUM2INT(RARRAY_PTR(val)[0]);
month = NUM2INT(RARRAY_PTR(val)[1]);
- oci_lc(OCIIntervalSetYearMonth(oci8_envhp, oci8_errhp,
- year, month, intvl));
+ if (oracle_client_version >= ORAVERNUM(9, 2, 0, 3, 0)) {
+ oci_lc(OCIIntervalSetYearMonth(oci8_envhp, oci8_errhp,
+ year, month, intvl));
+ } else {
+ /* Workaround for Bug 2227982 */
+ char buf[64];
+ char *sign = "";
+
+ if (year < 0 && month != 0) {
+ year += 1;
+ month -= 12;
+ }
+ if (year < 0 || month < 0) {
+ sign = "-";
+ year = -year;
+ month = -month;
+ }
+ sprintf(buf, "%s%d-%d", sign, year, month);
+ oci_lc(OCIIntervalFromText(oci8_envhp, oci8_errhp, (text*)buf, strlen(buf), intvl));
+ }
return intvl;
}
@@ -357,8 +375,32 @@
minute = NUM2INT(RARRAY_PTR(val)[2]);
sec = NUM2INT(RARRAY_PTR(val)[3]);
fsec = NUM2INT(RARRAY_PTR(val)[4]);
- oci_lc(OCIIntervalSetDaySecond(oci8_envhp, oci8_errhp,
- day, hour, minute, sec, fsec, intvl));
+ if (oracle_client_version >= ORAVERNUM(9, 2, 0, 3, 0)) {
+ oci_lc(OCIIntervalSetDaySecond(oci8_envhp, oci8_errhp,
+ day, hour, minute, sec, fsec, intvl));
+ } else {
+ /* Workaround for Bug 2227982 */
+ char buf[64];
+ char *sign = "";
+
+ if (day == 0) {
+ if (hour < 0) {
+ sign = "-";
+ hour = -hour;
+ } else if (minute < 0) {
+ sign = "-";
+ minute = -minute;
+ } else if (sec < 0) {
+ sign = "-";
+ sec = -sec;
+ } else if (fsec < 0) {
+ sign = "-";
+ fsec = -fsec;
+ }
+ }
+ sprintf(buf, "%s%d %02d:%02d:%02d.%09d", sign, day, hour, minute, sec, fsec);
+ oci_lc(OCIIntervalFromText(oci8_envhp, oci8_errhp, (text*)buf, strlen(buf), intvl));
+ }
return intvl;
}
Modified: trunk/ruby-oci8/lib/oci8/datetime.rb
===================================================================
--- trunk/ruby-oci8/lib/oci8/datetime.rb 2009-10-17 14:23:12 UTC (rev 364)
+++ trunk/ruby-oci8/lib/oci8/datetime.rb 2009-10-18 13:46:48 UTC (rev 365)
@@ -427,21 +427,27 @@
# OCI8::BindType::IntervalYM
#++
#
- # This is a helper class to bind ruby's
- # Integer[http://www.ruby-doc.org/core/classes/Integer.html]
- # object as Oracle's <tt>INTERVAL YEAR TO MONTH</tt> datatype.
+ # This is a helper class to select or bind Oracle data type
+ # <tt>INTERVAL YEAR TO MONTH</tt>. The retrieved value is
+ # the number of months between two timestamps.
#
- # == Select
+ # The value can be applied to \DateTime#>> to shift months.
+ # It can be applied to \Time#months_since if activisupport has
+ # been loaded.
#
- # The fetched value for a <tt>INTERVAL YEAR TO MONTH</tt> column
- # is an Integer[http://www.ruby-doc.org/core/classes/Integer.html]
- # which means the months between two timestamps.
+ # === How to select <tt>INTERVAL YEAR TO MONTH</tt>
#
- # == Bind
+ # <tt>INTERVAL YEAR TO MONTH</tt> is selected as an Integer.
#
- # You cannot bind as <tt>INTERVAL YEAR TO MONTH</tt> implicitly.
- # It must be bound explicitly with :interval_ym.
+ # conn.exec("select (current_timestamp - hiredate) year to month from emp") do |hired_months|
+ # puts "hired_months = #{hired_months}"
+ # end
#
+ # == How to bind <tt>INTERVAL YEAR TO MONTH</tt>
+ #
+ # You cannot bind a bind variable as <tt>INTERVAL YEAR TO MONTH</tt> implicitly.
+ # It must be bound explicitly by OCI8::Cursor#bind_param.
+ #
# # output bind variable
# cursor = conn.parse(<<-EOS)
# BEGIN
@@ -487,51 +493,43 @@
# OCI8::BindType::IntervalDS
#++
#
- # This is a helper class to bind ruby's
- # Rational[http://www.ruby-doc.org/core/classes/Rational.html]
- # object as Oracle's <tt>INTERVAL DAY TO SECOND</tt> datatype.
+ # (new in 2.0)
#
- # == Select
+ # This is a helper class to select or bind Oracle data type
+ # <tt>INTERVAL DAY TO SECOND</tt>. The retrieved value is
+ # the number of seconds between two typestamps as a \Float.
#
- # The fetched value for a <tt>INTERVAL DAY TO SECOND</tt> column
- # is a Rational[http://www.ruby-doc.org/core/classes/Rational.html]
- # or an Integer[http://www.ruby-doc.org/core/classes/Integer.html].
- # The value is usable to apply to
- # DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]#+ and
- # DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]#-.
+ # Note that it is the number days as a \Rational if
+ # OCI8::BindType::IntervalDS.unit is :day or the ruby-oci8
+ # version is prior to 2.0.3.
#
- # == Bind
+ # == How to bind <tt>INTERVAL DAY TO SECOND</tt>
#
- # You cannot bind as <tt>INTERVAL YEAR TO MONTH</tt> implicitly.
- # It must be bound explicitly with :interval_ds.
+ # You cannot bind a bind variable as <tt>INTERVAL DAY TO SECOND</tt>
+ # implicitly. It must be bound explicitly by OCI8::Cursor#bind_param.
#
- # # output
- # ts1 = DateTime.parse('1969-11-19 06:54:35 00:00')
- # ts2 = DateTime.parse('1969-07-20 20:17:40 00:00')
+ # # output bind variable
# cursor = conn.parse(<<-EOS)
# BEGIN
- # :itv := (:ts1 - :ts2) DAY TO SECOND;
+ # :interval := (:ts1 - :ts2) DAY TO SECOND(9);
# END;
# EOS
- # cursor.bind_param(:itv, nil, :interval_ds)
- # cursor.bind_param(:ts1, ts1)
- # cursor.bind_param(:ts2, ts2)
+ # cursor.bind_param(:interval, nil, :interval_ds)
+ # cursor.bind_param(:ts1, DateTime.parse('1969-11-19 06:54:35 00:00'))
+ # cursor.bind_param(:ts2, DateTime.parse('1969-07-20 20:17:40 00:00'))
# cursor.exec
- # cursor[:itv] # == ts1 - ts2
+ # cursor[:interval] # => 10492615.0 seconds
# cursor.close
#
- # # input
- # ts2 = DateTime.parse('1969-07-20 20:17:40 00:00')
- # itv = 121 + 10.to_r/24 + 36.to_r/(24*60) + 55.to_r/(24*60*60)
- # # 121 days, 10 hours, 36 minutes, 55 seconds
+ # # input bind variable
# cursor = conn.parse(<<-EOS)
# BEGIN
- # :ts1 := :ts2 + :itv;
+ # :ts1 := :ts2 + :interval;
# END;
# EOS
# cursor.bind_param(:ts1, nil, DateTime)
- # cursor.bind_param(:ts2, ts2)
- # cursor.bind_param(:itv, itv, :interval_ds)
+ # cursor.bind_param(:ts2, DateTime.parse('1969-07-20 20:17:40 00:00'))
+ # cursor.bind_param(:interval, 10492615.0, :interval_ds)
# cursor.exec
# cursor[:ts1].strftime('%Y-%m-%d %H:%M:%S') # => 1969-11-19 06:54:35
# cursor.close
@@ -541,7 +539,33 @@
@@minute = @@hour / 60
@@sec = @@minute / 60
@@fsec = @@sec / 1000000000
+ @@unit = :second
+ # call-seq:
+ # OCI8::BindType::IntervalDS.unit -> :second or :day
+ #
+ # (new in 2.0.3)
+ #
+ # Retrieves the unit of interval.
+ def self.unit
+ @@unit
+ end
+
+ # call-seq:
+ # OCI8::BindType::IntervalDS.unit = :second or :day
+ #
+ # (new in 2.0.3)
+ #
+ # Changes the unit of interval. :second is the default.
+ def self.unit=(val)
+ case val
+ when :second, :day
+ @@unit = val
+ else
+ raise 'unit should be :second or :day'
+ end
+ end
+
def set(val) # :nodoc:
unless val.nil?
if val < 0
@@ -550,10 +574,17 @@
else
is_minus = false
end
- day, val = val.divmod 1
- hour, val = (val * 24).divmod 1
- minute, val = (val * 60).divmod 1
- sec, val = (val * 60).divmod 1
+ if @@unit == :second
+ day, val = val.divmod 86400
+ hour, val = val.divmod 3600
+ minute, val = val.divmod 60
+ sec, val = val.divmod 1
+ else
+ day, val = val.divmod 1
+ hour, val = (val * 24).divmod 1
+ minute, val = (val * 60).divmod 1
+ sec, val = (val * 60).divmod 1
+ end
fsec, val = (val * 1000000000).divmod 1
if is_minus
day = - day
@@ -571,7 +602,12 @@
val = super()
return nil if val.nil?
day, hour, minute, sec, fsec = val
- day + (hour * @@hour) + (minute * @@minute) + (sec * @@sec) + (fsec * @@fsec)
+ if @@unit == :second
+ fsec = fsec / 1000000000.0
+ day * 86400 + hour * 3600 + minute * 60 + sec + fsec
+ else
+ day + (hour * @@hour) + (minute * @@minute) + (sec * @@sec) + (fsec * @@fsec)
+ end
end
end # OCI8::BindType::IntervalDS
end
Modified: trunk/ruby-oci8/test/test_datetime.rb
===================================================================
--- trunk/ruby-oci8/test/test_datetime.rb 2009-10-17 14:23:12 UTC (rev 364)
+++ trunk/ruby-oci8/test/test_datetime.rb 2009-10-18 13:46:48 UTC (rev 365)
@@ -13,6 +13,31 @@
end
end
+ def string_to_time(str)
+ /(\d+)-(\d+)-(\d+) ?(?:(\d+):(\d+):(\d+))?(?:\.(\d+))? ?([+-]\d+:\d+)?/ =~ str
+ args = []
+ args << $1.to_i # year
+ args << $2.to_i # month
+ args << $3.to_i # day
+ args << $4.to_i if $4 # hour
+ args << $5.to_i if $5 # minute
+ if $8
+ args << $6.to_i + $7.to_i.to_r / ('1' + '0' * ($7.length)).to_i
+ args << $8
+ Time.new(*args)
+ else
+ if $6
+ args << $6.to_i
+ end
+ if $7
+ args << $7.to_i.to_r * 1000000 / ('1' + '0' * ($7.length)).to_i
+ end
+ # no time zone
+ Time.local(*args)
+ end
+ #Time.local(*str.split(/[- :\.]/).collect do |n| n.to_i; end)
+ end
+
def setup
@conn = get_oci8_connection
@local_timezone = timezone_string(*((::Time.now.utc_offset / 60).divmod 60))
@@ -129,7 +154,12 @@
@conn.exec(<<-EOS) do |row|
SELECT TO_TIMESTAMP_TZ('#{date}', 'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM') FROM dual
EOS
- assert_equal(DateTime.parse(date), row[0])
+ expected_val = begin
+ string_to_time(date)
+ rescue
+ DateTime.parse(date)
+ end
+ assert_equal(expected_val, row[0])
end
end
end
@@ -274,6 +304,7 @@
when Time
assert_equal(tz, timezone_string(*((dt.utc_offset / 60).divmod 60)))
when DateTime
+ tz = tz.gsub(/:/, '') if RUBY_VERSION <= '1.8.5'
assert_equal(tz, dt.zone)
else
flunk "unexpedted type #{dt.class}"
@@ -352,9 +383,14 @@
cursor.bind_param(:out, nil, String, 36)
cursor.bind_param(:in1, nil, String, 36)
cursor.bind_param(:in2, nil, :interval_ym)
- [['2006-01-01', -22],
- ['2006-01-01', -10],
+ [['2006-01-01', -25],
+ ['2006-01-01', -24],
+ ['2006-01-01', -23],
+ ['2006-01-01', -13],
+ ['2006-01-01', -12],
+ ['2006-01-01', -11],
['2006-01-01', +2],
+ ['2006-01-01', -2],
['2006-01-01', +12]
].each do |date, interval|
cursor[:in1] = date
@@ -388,7 +424,7 @@
- TO_TIMESTAMP('#{date2}', 'YYYY-MM-DD HH24:MI:SS.FF')) DAY(3) TO SECOND
FROM dual
EOS
- assert_equal(DateTime.parse(date1) - DateTime.parse(date2), row[0])
+ assert_in_delta(string_to_time(date1) - string_to_time(date2), row[0], 0.0000000001)
end
end
end
@@ -427,7 +463,7 @@
cursor[:in1] = date1
cursor[:in2] = date2
cursor.exec
- assert_equal(DateTime.parse(date1) - DateTime.parse(date2), cursor[:out])
+ assert_in_delta(string_to_time(date1) - string_to_time(date2), cursor[:out], 0.0000000001)
end
cursor.close
end
@@ -440,7 +476,7 @@
ts1 TIMESTAMP;
BEGIN
ts1 := TO_TIMESTAMP(:in1, 'YYYY-MM-DD HH24:MI:SS.FF');
- :out := TO_CHAR(ts1 + :in2, 'YYYY-MM-DD HH24:MI:SS.FF');
+ :out := TO_CHAR(ts1 + :in2, 'YYYY-MM-DD HH24:MI:SS.FF6');
END;
EOS
cursor.bind_param(:out, nil, String, 36)
@@ -459,11 +495,128 @@
['2006-01-01', +1.to_r / (24*60*60)], # one second
['2006-01-01', +999999.to_r / (24*60*60*1000000)] # 0.999999 seconds
].each do |date, interval|
+ interval *= 86400
cursor[:in1] = date
cursor[:in2] = interval
cursor.exec
- assert_equal(DateTime.parse(date) + interval, DateTime.parse(cursor[:out]))
+ assert_equal(string_to_time(date) + interval, string_to_time(cursor[:out]))
end
cursor.close
end
+
+ def test_days_interval_ds_select
+ return if $oracle_version < OCI8::ORAVER_9_0
+
+ [['2006-01-01', '2004-03-01'],
+ ['2006-01-01', '2005-03-01'],
+ ['2006-01-01', '2006-03-01'],
+ ['2006-01-01', '2007-03-01'],
+ ['2006-01-01', '2006-01-01 23:00:00'],
+ ['2006-01-01', '2006-01-01 00:59:00'],
+ ['2006-01-01', '2006-01-01 00:00:59'],
+ ['2006-01-01', '2006-01-01 00:00:00.999999'],
+ ['2006-01-01', '2006-01-01 23:59:59.999999'],
+ ['2006-01-01', '2005-12-31 23:00:00'],
+ ['2006-01-01', '2005-12-31 00:59:00'],
+ ['2006-01-01', '2005-12-31 00:00:59'],
+ ['2006-01-01', '2005-12-31 00:00:00.999999'],
+ ['2006-01-01', '2005-12-31 23:59:59.999999']
+ ].each do |date1, date2|
+ begin
+ OCI8::BindType::IntervalDS.unit = :day
+ @conn.exec(<<-EOS) do |row|
+SELECT (TO_TIMESTAMP('#{date1}', 'YYYY-MM-DD HH24:MI:SS.FF')
+ - TO_TIMESTAMP('#{date2}', 'YYYY-MM-DD HH24:MI:SS.FF')) DAY(3) TO SECOND
+ FROM dual
+EOS
+ assert_equal(DateTime.parse(date1) - DateTime.parse(date2), row[0])
+ end
+ ensure
+ OCI8::BindType::IntervalDS.unit = :second
+ end
+ end
+ end
+
+ def test_days_interval_ds_out_bind
+ return if $oracle_version < OCI8::ORAVER_9_0
+
+ cursor = @conn.parse(<<-EOS)
+DECLARE
+ ts1 TIMESTAMP;
+ ts2 TIMESTAMP;
+BEGIN
+ ts1 := TO_TIMESTAMP(:in1, 'YYYY-MM-DD HH24:MI:SS.FF');
+ ts2 := TO_TIMESTAMP(:in2, 'YYYY-MM-DD HH24:MI:SS.FF');
+ :out := (ts1 - ts2) DAY TO SECOND(9);
+END;
+EOS
+ cursor.bind_param(:out, nil, :interval_ds)
+ cursor.bind_param(:in1, nil, String, 36)
+ cursor.bind_param(:in2, nil, String, 36)
+ [['2006-01-01', '2004-03-01'],
+ ['2006-01-01', '2005-03-01'],
+ ['2006-01-01', '2006-03-01'],
+ ['2006-01-01', '2007-03-01'],
+ ['2006-01-01', '2006-01-01 23:00:00'],
+ ['2006-01-01', '2006-01-01 00:59:00'],
+ ['2006-01-01', '2006-01-01 00:00:59'],
+ ['2006-01-01', '2006-01-01 00:00:00.999999'],
+ ['2006-01-01', '2006-01-01 23:59:59.999999'],
+ ['2006-01-01', '2005-12-31 23:00:00'],
+ ['2006-01-01', '2005-12-31 00:59:00'],
+ ['2006-01-01', '2005-12-31 00:00:59'],
+ ['2006-01-01', '2005-12-31 00:00:00.999999'],
+ ['2006-01-01', '2005-12-31 23:59:59.999999']
+ ].each do |date1, date2|
+ begin
+ OCI8::BindType::IntervalDS.unit = :day
+ cursor[:in1] = date1
+ cursor[:in2] = date2
+ cursor.exec
+ assert_equal(DateTime.parse(date1) - DateTime.parse(date2), cursor[:out])
+ ensure
+ OCI8::BindType::IntervalDS.unit = :second
+ end
+ end
+ cursor.close
+ end
+
+ def test_days_interval_ds_in_bind
+ return if $oracle_version < OCI8::ORAVER_9_0
+
+ cursor = @conn.parse(<<-EOS)
+DECLARE
+ ts1 TIMESTAMP;
+BEGIN
+ ts1 := TO_TIMESTAMP(:in1, 'YYYY-MM-DD');
+ :out := TO_CHAR(ts1 + :in2, 'YYYY-MM-DD HH24:MI:SS.FF');
+END;
+EOS
+ cursor.bind_param(:out, nil, String, 36)
+ cursor.bind_param(:in1, nil, String, 36)
+ cursor.bind_param(:in2, nil, :interval_ds)
+ [['2006-01-01', -22],
+ ['2006-01-01', -10],
+ ['2006-01-01', +2],
+ ['2006-01-01', +12],
+ ['2006-01-01', -1.to_r / 24], # one hour
+ ['2006-01-01', -1.to_r / (24*60)], # one minute
+ ['2006-01-01', -1.to_r / (24*60*60)], # one second
+ ['2006-01-01', -999999.to_r / (24*60*60*1000000)], # 0.999999 seconds
+ ['2006-01-01', +1.to_r / 24], # one hour
+ ['2006-01-01', +1.to_r / (24*60)], # one minute
+ ['2006-01-01', +1.to_r / (24*60*60)], # one second
+ ['2006-01-01', +999999.to_r / (24*60*60*1000000)] # 0.999999 seconds
+ ].each do |date, interval|
+ begin
+ OCI8::BindType::IntervalDS.unit = :day
+ cursor[:in1] = date
+ cursor[:in2] = interval
+ cursor.exec
+ assert_equal(DateTime.parse(date) + interval, DateTime.parse(cursor[:out]))
+ ensure
+ OCI8::BindType::IntervalDS.unit = :second
+ end
+ end
+ end
end # TestOCI8
More information about the ruby-oci8-commit
mailing list