[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