Add a proof of concept test for copy() with assymetric IC::DT
Tina Mueller [Fri, 14 Aug 2015 11:51:53 +0000 (13:51 +0200)]
What a mess. The core of the problem is that some of our IC::DT in/deflator
pairs are *not* symmetric. That is for things to roundtrip one needs values
to pass through the database, which is configured "just properly wrong" to
perform the second half of this evil dance.

Of course this break copy() and likely other things I do not know about.
Given there is nothing one can do about the core problem (huge install base)
a minimally invasive workaround has been devised and tested here. Refer to
`git show 993fa9b | perl -ne 'print if 110..134'` for the exact snippet you
need to place in your base result class to make everything work again.

This is terrible.
    -- ribasushi

For completeness: here is a full list of individual inflators (as of Apr
2016) and which ones are broken beyond repair:

ACCESS                         2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
DB2                            2016-04-13-07.42.58         2016-04-13T07:42:58   2016-04-13-07.42.58        (via DateTime::Format::DB2)
MSSQL                          2016-04-13 07:42:58.000     2016-04-13T07:42:58   2016-04-13 07:42:58.000    (via DBIx::Class::Storage::DBI::MSSQL::DateTime::Format)
Pg                             2016-04-13 07:42:58+0000    2016-04-13T07:42:58   2016-04-13 07:42:58+0000   (via DateTime::Format::Pg)
ADO                            2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
NoBindVars                     2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
SQLAnywhere                    2016-04-13 07:42:58.000000  2016-04-13T07:42:58   2016-04-13 07:42:58.000000 (via DateTime::Format::Strptime)
AutoCast                       2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
Firebird                       2016-04-13 07:42:58.0000    2016-04-13T07:42:58   2016-04-13 07:42:58.0000   (via DBIx::Class::Storage::DBI::InterBase::DateTime::Format)
Informix                       2016-04-13 07:42:58.00000   2016-04-13T07:42:58   2016-04-13 07:42:58.00000  (via DBIx::Class::Storage::DBI::Informix::DateTime::Format)
ODBC                           2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
Sybase                         2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
mysql                          2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
InterBase                      2016-04-13 07:42:58.0000    2016-04-13T07:42:58   2016-04-13 07:42:58.0000   (via DBIx::Class::Storage::DBI::InterBase::DateTime::Format)
Oracle                         2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
SQLite                         2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::SQLite)
Firebird::Common               2016-04-13 07:42:58.0000    2016-04-13T07:42:58   2016-04-13 07:42:58.0000   (via DBIx::Class::Storage::DBI::InterBase::DateTime::Format)
Sybase::FreeTDS                2016-04-13 07:42:58         2016-04-13T07:42:58   2016-04-13 07:42:58        (via DateTime::Format::MySQL)
Sybase::MSSQL                  2016-04-13 07:42:58.000     Your datetime does not match your pattern. at                             (via DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::DateTime::Format)
Sybase::Microsoft_SQL_Server   2016-04-13 07:42:58.000     Your datetime does not match your pattern. at                             (via DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::DateTime::Format)
Sybase::ASE                    04/13/2016 07:42:58.000     Your datetime does not match your pattern. at                             (via DBIx::Class::Storage::DBI::Sybase::ASE::DateTime::Format)
Sybase::Microsoft_SQL_Server::NoBindVars 2016-04-13 07:42:59.000     Your datetime does not match your pattern. at                             (via DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::DateTime::Format)
Sybase::ASE::NoBindVars        04/13/2016 07:42:59.000     Your datetime does not match your pattern. at                             (via DBIx::Class::Storage::DBI::Sybase::ASE::DateTime::Format)
Oracle::WhereJoins             2016-04-13 07:42:59         2016-04-13T07:42:59   2016-04-13 07:42:59        (via DateTime::Format::Oracle)
Oracle::Generic                2016-04-13 07:42:59         2016-04-13T07:42:59   2016-04-13 07:42:59        (via DateTime::Format::Oracle)
ODBC::ACCESS                   2016-04-13 07:42:59         2016-04-13T07:42:59   2016-04-13 07:42:59        (via DBIx::Class::Storage::DBI::ODBC::ACCESS::DateTime::Format)
ODBC::DB2_400_SQL              2016-04-13-07.42.59         2016-04-13T07:42:59   2016-04-13-07.42.59        (via DateTime::Format::DB2)
ODBC::SQL_Anywhere             2016-04-13 07:42:59.000000  2016-04-13T07:42:59   2016-04-13 07:42:59.000000 (via DateTime::Format::Strptime)
ODBC::Firebird                 2016-04-13 07:42:59.0000    2016-04-13T07:42:59   2016-04-13 07:42:59.0000   (via DBIx::Class::Storage::DBI::InterBase::DateTime::Format)
ODBC::Microsoft_SQL_Server     2016-04-13 07:42:59.000     2016-04-13T07:42:59   2016-04-13 07:42:59.000    (via DBIx::Class::Storage::DBI::MSSQL::DateTime::Format)
ADO::MS_Jet                    04/13/2016 07:42:59 AM      2016-04-13T07:42:59   04/13/2016 07:42:59 AM     (via DBIx::Class::Storage::DBI::ADO::MS_Jet::DateTime::Format)
ADO::Microsoft_SQL_Server      04/13/2016 07:42:59 AM      2016-04-13T07:42:59   04/13/2016 07:42:59 AM     (via DBIx::Class::Storage::DBI::ADO::Microsoft_SQL_Server::DateTime::Format)

... and the program that produces the above ...

~$ perl -I lib -MDateTime -MFile::Find -e '

  find({ no_chdir => 1, follow_fast => 1, wanted => sub {

    -f _ or next;
    $_ =~ m{DBIx/Class/Storage/DBI/(?!Replicated|IdentityInsert|.*?Cursor|UniqueIdentifier)(.+)\.pm} or next;

    list_dt_state($1);

  }}, "lib" );

  sub list_dt_state {
    ( my $id = shift ) =~ s|/|::|g;

    my $s = "DBIx::Class::Storage::DBI::$id";

    my $p = eval "local \$SIG{__WARN__} = sub {}; require $s; $s->build_datetime_parser"
      or ( printf "%s: %s\n", $id, substr $@, 0, 45 and next );

    my $as_string = $p->format_datetime( DateTime->now );
    my $half_trip = eval { $p->parse_datetime( $as_string ) } || substr $@, 0, 45;
    my $full_trip = $@ ? "" : eval { $p->format_datetime( $half_trip ) } || substr $@, 0, 45;

    printf "%-30s %-26s  %-20s  %-26s (via %s)\n",
      $id,
      $as_string,
      $half_trip,
      $full_trip,
      ( ref $p || $p ),
    ;
  }
'

Changes
t/icdt/engine_specific/sybase.t

diff --git a/Changes b/Changes
index 2fc18e9..9f8ec85 100644 (file)
--- a/Changes
+++ b/Changes
@@ -62,6 +62,8 @@ Revision history for DBIx::Class
           autoinc value when inserting rows containing blobs (GH#82)
 
     * Misc
+        - Add explicit test for pathological example of asymmetric IC::DT setup
+          working with copy() in t/icdt/engine_specific/sybase.t (GH#84)
         - Fix invalid variable names in ResultSource::View examples
         - Typo fixes from downstream debian packagers (RT#112007)
         - Skip tests in a way more intelligent and speedy manner when optional
index 72a8bdb..993fa9b 100644 (file)
@@ -7,6 +7,7 @@ use warnings;
 use Test::More;
 use Test::Exception;
 use DBIx::Class::_Util 'scope_guard';
+use Sub::Name;
 
 use DBICTest;
 
@@ -94,7 +95,7 @@ SQL
           %$create_extra,
         }));
     ok( $row = $schema->resultset($source)
-      ->search({ $pk => $row->$pk }, { select => [$col] })
+      ->search({ $pk => $row->$pk }, { select => [$pk, $col] })
       ->first
     );
     is( $row->$col, $dt, "$type roundtrip" );
@@ -102,6 +103,46 @@ SQL
     cmp_ok( $row->$col->nanosecond, '==', $sample_dt->{nanosecond},
       'DateTime fractional portion roundtrip' )
       if exists $sample_dt->{nanosecond};
+
+    # Testing an ugly half-solution
+    #
+    # copy() uses get_columns()
+    #
+    # The values should survive a roundtrip also, but they don't
+    # because the Sybase ICDT setup is asymmetric
+    # One *has* to force an inflation/deflation cycle to make the
+    # values usable to the database
+    #
+    # This can be done by marking the columns as dirty, and there
+    # are tests for this already in t/inflate/serialize.t
+    #
+    # But even this isn't enough - one has to reload the RDBMS-formatted
+    # values once done, otherwise the copy is just as useless... sigh
+    #
+    # Adding the test here to validate the technique works
+    # UGH!
+    {
+      no warnings 'once';
+      local *DBICTest::BaseResult::copy = subname 'DBICTest::BaseResult::copy' => sub {
+        my $self = shift;
+
+        $self->make_column_dirty($_) for keys %{{ $self->get_inflated_columns }};
+
+        my $cp = $self->next::method(@_);
+
+        $cp->discard_changes({ columns => [ keys %{{ $cp->get_columns }} ] });
+      };
+      Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO;
+
+      my $cp = $row->copy;
+      ok( $cp->in_storage );
+      is( $cp->$col, $dt, "$type copy logical roundtrip" );
+
+      $cp->discard_changes({ select => [ $pk, $col ] });
+      is( $cp->$col, $dt, "$type copy server roundtrip" );
+    }
+
+    Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO;
   }
 
   # test a computed datetime column