dt_$foo works for Oracle
Alexander Hartmaier [Fri, 11 Mar 2011 18:12:35 +0000 (19:12 +0100)]
lib/DBIx/Class/SQLMaker/Oracle.pm
t/sqlmaker/op_dt.t

index aa6a3ba..22cbf71 100644 (file)
@@ -248,73 +248,88 @@ sub _insert_returning {
 
 {
   my %part_map = (
-     month            => 'MONTH',
-     day_of_month     => 'DAYOFMONTH',
-     day_of_year      => 'DAYOFYEAR',
-     day_of_quarter   => 'DAY_OF_QUARTER',
-     quarter          => 'QUARTER_OF_YEAR',
-     month_of_quarter => 'MONTH_OF_QUARTER',
-     year             => 'YEAR',
-     hour             => 'HOUR',
-     minute           => 'MINUTE',
-     second           => 'SECOND',
-     week_of_quarter  => 'WEEK_OF_QUARTER',
-     week_of_year     => 'WEEK_OF_YEAR',
+    second       => 'SECOND',
+    minute       => 'MINUTE',
+    hour         => 'HOUR',
+    day_of_month => 'DAY',
+    month        => 'MONTH',
+    year         => 'YEAR',
   );
 
   sub _datetime_sql {
     die $_[0]->_unsupported_date_extraction($_[1], 'Oracle')
        unless exists $part_map{$_[1]};
-    "$part_map{$_[1]}($_[2])"
+    "EXTRACT($part_map{$_[1]} FROM $_[2])"
   }
 }
 
 {
   my %part_map = (
-     second      => 'SQL_TSI_SECOND',
-     minute      => 'SQL_TSI_MINUTE',
-     hour        => 'SQL_TSI_HOUR',
-     week        => 'SQL_TSI_WEEK',
-     quarter     => 'SQL_TSI_QUARTER',
-     month       => 'SQL_TSI_MONTH',
-     day_of_year => 'SQL_TSI_DAY',
-     year        => 'SQL_TSI_YEAR',
+    second       => '* 31 * 24 * 60 * 60',
+    minute       => '* 31 * 24 * 60',
+    hour         => '* 31 * 24',
+    day          => '* 31',
+    month        => undef,
+    year         => '/ 12',
   );
 
   sub _datetime_diff_sql {
     die $_[0]->_unsupported_date_diff($_[1], 'Oracle')
        unless exists $part_map{$_[1]};
-    "TIMESTAMPDIFF($part_map{$_[1]}, $_[2], $_[3])"
+    my $sql = "MONTHS_BETWEEN($_[2], $_[3])";
+    $sql .= " $part_map{$_[1]}"
+      if defined $part_map{$_[1]};
+    $sql = "TRUNC($sql)";
+    return $sql;
   }
 }
 
+{
+  my %part_map = (
+    second       => 'NUMTODSINTERVAL',
+    minute       => 'NUMTODSINTERVAL',
+    hour         => 'NUMTODSINTERVAL',
+    day          => 'NUMTODSINTERVAL',
+    month        => 'NUMTOYMINTERVAL',
+    year         => 'NUMTOYMINTERVAL',
+  );
+
+  sub _datetime_add_sql {
+    my ($self, $part, $date, $amount) = @_;
+
+    die $self->_unsupported_date_adding($part, 'Oracle')
+      unless exists $part_map{$part};
+
+    return "($date + $part_map{$part}($amount, '$part'))";
+  }
+}
+
+sub _reorder_add_datetime_vars {
+   my ($self, $amount, $date) = @_;
+
+   return ($date, $amount);
+}
+
 =head1 DATE FUNCTION IMPLEMENTATION
 
-A separate function is used for each extraction type in Oracle.  The types
-supported are:
+The function used to extract date information is C<EXTRACT>, which supports
 
- month
+ second
+ minute
+ hour
  day_of_month
- day_of_year
- day_of_quarter
- quarter
- month_of_quarter
+ month
  year
- hour
- minute
- second
- week_of_quarter
- week_of_year
 
-The function used to diff dates is C<TIMESTAMPDIFF>, which supports
+The function used to diff dates is C<MONTHS_BETWEEN> with
+multiplication/division to the requested format.
+The valid formats are:
 
  second
  minute
  hour
- week
- quarter
+ day
  month
- day_of_year
  year
 
 =cut
index 7f7f59c..11bcc04 100644 (file)
@@ -17,6 +17,7 @@ my %dbs_to_test = (
    sqlite   => 1,
    mssql    => 0,
    postgres => 1,
+   oracle   => 1,
 );
 
 my %schema = (
@@ -77,7 +78,50 @@ $s;
       } else {
          DBICTest->init_schema( no_deploy=> 1, storage_type => '::DBI::Pg' )
       }
-   }
+   },
+   oracle =>  do {
+      my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_ORA_${_}" } qw/DSN USER PASS/};
+      if ($dsn && $user) {
+         my $s = DBICTest::Schema->connect($dsn, $user, $pass, { on_connect_call => 'datetime_setup' });
+         try { $s->storage->ensure_connected };
+
+         $s->storage->dbh_do (sub {
+             my ($storage, $dbh) = @_;
+             eval { $dbh->do("DROP TRIGGER trq_event_id") };
+             eval { $dbh->do("DROP SEQUENCE sq_event_id") };
+             eval { $dbh->do("DROP TABLE event") };
+             $dbh->do('CREATE SEQUENCE sq_event_id');
+             $dbh->do(<<'SQL');
+CREATE TABLE event (
+   id NUMBER NOT NULL,
+   starts_at DATE NOT NULL,
+   created_on TIMESTAMP NOT NULL,
+   varchar_date VARCHAR(20),
+   varchar_datetime VARCHAR(20),
+   skip_inflation TIMESTAMP,
+   ts_without_tz TIMESTAMP,
+   CONSTRAINT PK_EVENT PRIMARY KEY (id)
+)
+SQL
+             $dbh->do(<<'SQL');
+CREATE TRIGGER trg_event_id
+BEFORE INSERT ON event
+FOR EACH ROW WHEN (
+  new.id IS NULL OR new.id = 0
+)
+BEGIN
+  SELECT sq_event_id.nextval
+  INTO :new.id
+  FROM dual;
+  END;
+SQL
+        $dbs_to_test{oracle} = 1;
+});
+$s;
+      } else {
+         DBICTest->init_schema( no_deploy=> 1, storage_type => '::DBI::Oracle', on_connect_call => 'datetime_setup' )
+      }
+   },
 );
 
 my %rs = map { $_ => $schema{$_}->resultset('Event') } keys %schema;
@@ -100,6 +144,11 @@ $rs{postgres}->populate([
  ['2010-12-12', '2011-12-14 12:12:12', '2011-12-12 12:12:12'],
 ]) if $schema{postgres}->storage->connected;
 
+$rs{oracle}->populate([
+ [qw(starts_at created_on skip_inflation)],
+ ['2010-12-12', '2010-12-14 12:12:12', '2019-12-12 12:12:12'],
+ ['2010-12-12', '2011-12-14 12:12:12', '2011-12-12 12:12:12'],
+]) if $schema{oracle}->storage->connected;
 
 my $date = DateTime->new(
    year => 2010,
@@ -145,6 +194,12 @@ my @tests = (
       bind   => [[ 'me.created_on', '2010-12-14 12:12:12' ]],
       hri    => [hri_thing('2010-12-12', '2010-12-14 12:12:12', '2019-12-12 12:12:12')],
     },
+    oracle => {
+      select => 'me.starts_at, me.created_on, me.skip_inflation',
+      where  => 'me.created_on = ?',
+      bind   => [[ 'me.created_on', '2010-12-14 12:12:12' ]],
+      hri    => [hri_thing('2010-12-12 00:00:00', '2010-12-14 12:12:12.000000', '2019-12-12 12:12:12.000000')],
+    },
     msg => '-dt_now works',
   },
 ## -dt_year tests
@@ -170,6 +225,12 @@ my @tests = (
       bind   => [['me.id' => 1 ]],
       hri    => [{ year => 2010 }],
     },
+    oracle => {
+      select => "EXTRACT(year FROM me.created_on)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 1 ]],
+      hri    => [{ year => 2010 }],
+    },
     msg    => '-dt_year works',
   },
 ## -dt_get(year, month) tests
@@ -195,6 +256,12 @@ my @tests = (
       bind   => [['me.id' => 1 ]],
       hri    => [{ year => 2010, month => 12 }],
     },
+    oracle => {
+      select => "EXTRACT(year FROM me.created_on), EXTRACT(month FROM me.created_on)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 1 ]],
+      hri    => [{ year => 2010, month => 12 }],
+    },
     msg    => '-dt_get (year, month) works',
   },
 ## -dt_month tests
@@ -219,7 +286,12 @@ my @tests = (
       where => "me.id = ?",
       bind   => [['me.id' => 1 ]],
       hri    => [{ month => 12 }],
-
+    },
+    oracle => {
+      select => "EXTRACT(month FROM me.created_on)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 1 ]],
+      hri    => [{ month => 12 }],
     },
     msg    => '-dt_month works',
   },
@@ -245,7 +317,12 @@ my @tests = (
       where => "me.id = ?",
       bind   => [['me.id' => 1 ]],
       hri    => [{ day => 14 }],
-
+    },
+    oracle => {
+      select => "EXTRACT(day FROM me.created_on)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 1 ]],
+      hri    => [{ day => 14 }],
     },
     msg    => '-dt_day works',
   },
@@ -272,6 +349,12 @@ my @tests = (
       bind   => [['me.id' => 1 ]],
       hri    => [{ hour => 12 }],
     },
+    oracle => {
+      select => "EXTRACT(hour FROM me.created_on)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 1 ]],
+      hri    => [{ hour => 12 }],
+    },
     msg    => '-dt_hour works',
   },
 ## -dt_minute tests
@@ -297,6 +380,12 @@ my @tests = (
       bind   => [['me.id' => 1 ]],
       hri    => [{ minute => 12 }],
     },
+    oracle => {
+      select => "EXTRACT(minute FROM me.created_on)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 1 ]],
+      hri    => [{ minute => 12 }],
+    },
     msg    => '-dt_minute works',
   },
 ## -dt_second tests
@@ -321,7 +410,12 @@ my @tests = (
       where => "me.id = ?",
       bind   => [['me.id' => 1 ]],
       hri    => [{ second => 12 }],
-
+    },
+    oracle => {
+      select => "EXTRACT(second FROM me.created_on)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 1 ]],
+      hri    => [{ second => 12 }],
     },
     msg    => '-dt_second works',
   },
@@ -348,6 +442,12 @@ my @tests = (
       bind   => [['me.id' => 2 ]],
       hri => [{ sec_diff => 2*24*60*60 }],
     },
+    oracle => {
+      select   => "TRUNC(MONTHS_BETWEEN(me.created_on, me.skip_inflation) * 31 * 24 * 60 * 60)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 2 ]],
+      hri => [{ sec_diff => 2*24*60*60 }],
+    },
     msg    => '-dt_diff (second) works',
   },
 
@@ -374,11 +474,17 @@ my @tests = (
       bind   => [['me.id' => 2 ]],
       hri => [{ day_diff => 2 }],
     },
-
+    oracle => {
+      select   => "TRUNC(MONTHS_BETWEEN(me.created_on, me.skip_inflation) * 31)",
+      where => "me.id = ?",
+      bind   => [['me.id' => 2 ]],
+      hri => [{ day_diff => 2 }],
+    },
     msg    => '-dt_diff (day) works',
   },
 
   {
+    msg    => '-dt_add (year) works',
     search => { 'me.id' => 2 },
     select   => [ [ -dt_add => [year => 3, { -ident => 'me.created_on' } ] ] ],
     as   => [ 'date' ],
@@ -395,10 +501,16 @@ my @tests = (
       hri    => [{ date => '2014-12-14 12:12:12.000' }],
       skip   => 'need working bindtypes',
     },
-    msg    => '-dt_add (year) works',
+    oracle => {
+      select => "(me.created_on + NUMTOYMINTERVAL(?, 'year'))",
+      where => "me.id = ?",
+      bind   => [['', 3], ['me.id' => 2 ]],
+      hri    => [{ date => '2014-12-14 12:12:12.000000000' }],
+    },
   },
 
   {
+    msg    => '-dt_add (month) works',
     search => { 'me.id' => 2 },
     select   => [ [ -dt_add => [month => 3, { -ident => 'me.created_on' } ] ] ],
     as   => [ 'date' ],
@@ -415,10 +527,16 @@ my @tests = (
       hri    => [{ date => '2012-03-14 12:12:12.000' }],
       skip   => 'need working bindtypes',
     },
-    msg    => '-dt_add (month) works',
+    oracle => {
+      select => "(me.created_on + NUMTOYMINTERVAL(?, 'month'))",
+      where => "me.id = ?",
+      bind   => [['', 3], ['me.id' => 2 ]],
+      hri    => [{ date => '2012-03-14 12:12:12.000000000' }],
+    },
   },
 
   {
+    msg    => '-dt_add (day) works',
     search => { 'me.id' => 2 },
     select   => [ [ -dt_add => [day => 3, { -ident => 'me.created_on' } ] ] ],
     as   => [ 'date' ],
@@ -435,10 +553,16 @@ my @tests = (
       hri    => [{ date => '2011-12-17 12:12:12.000' }],
       skip   => 'need working bindtypes',
     },
-    msg    => '-dt_add (day) works',
+    oracle => {
+      select => "(me.created_on + NUMTODSINTERVAL(?, 'day'))",
+      where => "me.id = ?",
+      bind   => [['', 3], ['me.id' => 2 ]],
+      hri    => [{ date => '2011-12-17 12:12:12.000000000' }],
+    },
   },
 
   {
+    msg    => '-dt_add (hour) works',
     search => { 'me.id' => 2 },
     select   => [ [ -dt_add => [hour => 3, { -ident => 'me.created_on' } ] ] ],
     as   => [ 'date' ],
@@ -455,10 +579,16 @@ my @tests = (
       hri    => [{ date => '2011-12-14 15:12:12.000' }],
       skip   => 'need working bindtypes',
     },
-    msg    => '-dt_add (hour) works',
+    oracle => {
+      select => "(me.created_on + NUMTODSINTERVAL(?, 'hour'))",
+      where => "me.id = ?",
+      bind   => [['', 3], ['me.id' => 2 ]],
+      hri    => [{ date => '2011-12-14 15:12:12.000000000' }],
+    },
   },
 
   {
+    msg    => '-dt_add (minute) works',
     search => { 'me.id' => 2 },
     select   => [ [ -dt_add => [minute => 3, { -ident => 'me.created_on' } ] ] ],
     as   => [ 'date' ],
@@ -475,10 +605,16 @@ my @tests = (
       hri    => [{ date => '2011-12-14 12:15:12.000' }],
       skip   => 'need working bindtypes',
     },
-    msg    => '-dt_add (minute) works',
+    oracle => {
+      select => "(me.created_on + NUMTODSINTERVAL(?, 'minute'))",
+      where => "me.id = ?",
+      bind   => [['', 3], ['me.id' => 2 ]],
+      hri    => [{ date => '2011-12-14 12:15:12.000000000' }],
+    },
   },
 
   {
+    msg    => '-dt_add (second) works',
     search => { 'me.id' => 2 },
     select   => [ [ -dt_add => [second => 3, { -ident => 'me.created_on' } ] ] ],
     as   => [ 'date' ],
@@ -495,10 +631,16 @@ my @tests = (
       hri    => [{ date => '2011-12-14 12:12:15.000' }],
       skip   => 'need working bindtypes',
     },
-    msg    => '-dt_add (second) works',
+    oracle => {
+      select => "(me.created_on + NUMTODSINTERVAL(?, 'second'))",
+      where => "me.id = ?",
+      bind   => [['', 3], ['me.id' => 2 ]],
+      hri    => [{ date => '2011-12-14 12:12:15.000000000' }],
+    },
   },
 
   {
+    msg    => 'nested -dt_add works',
     search => { 'me.id' => 2 },
     select   => [ [ -dt_add => [second => 3, { -dt_add => [ day => 1, { -ident => 'me.created_on' } ] } ] ] ],
     as       => [ 'date' ],
@@ -515,10 +657,16 @@ my @tests = (
       hri    => [{ date => '2011-12-15 12:12:15.000' }],
       skip   => 'need working bindtypes',
     },
-    msg    => 'nested -dt_add works',
+    oracle => {
+      select => "((me.created_on + NUMTODSINTERVAL(?, 'day')) + NUMTODSINTERVAL(?, 'second'))",
+      where => "me.id = ?",
+      bind   => [['', 1], [ '', 3 ], ['me.id', 2]],
+      hri    => [{ date => '2011-12-15 12:12:15.000000000' }],
+    },
   },
 
   {
+    msg => '-dt_diff (year) works',
     search => { 'me.id' => 2 },
     select   => [ [ -dt_diff => [year => \'me.starts_at', { -ident => 'me.created_on' } ] ] ],
     as       => [ 'year' ],
@@ -531,7 +679,12 @@ my @tests = (
       bind   => [['me.id', 2]],
       hri    => [{ year => -1 }],
     },
-    msg => '-dt_diff (year) works',
+    oracle => {
+      select   => "TRUNC(MONTHS_BETWEEN(me.starts_at, me.created_on) / 12)",
+      where => "me.id = ?",
+      bind   => [['me.id', 2]],
+      hri    => [{ year => -1 }],
+    },
   },
 );