Add a proof of concept test for copy() with assymetric IC::DT
[dbsrgits/DBIx-Class.git] / t / icdt / engine_specific / sybase.t
1 BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) }
2 use DBIx::Class::Optional::Dependencies -skip_all_without => qw( ic_dt test_rdbms_ase );
3
4 use strict;
5 use warnings;
6
7 use Test::More;
8 use Test::Exception;
9 use DBIx::Class::_Util 'scope_guard';
10 use Sub::Name;
11
12 use DBICTest;
13
14 my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_SYBASE_${_}" } qw/DSN USER PASS/};
15
16 DBICTest::Schema->load_classes('EventSmallDT');
17
18 my @storage_types = (
19   'DBI::Sybase::ASE',
20   'DBI::Sybase::ASE::NoBindVars',
21 );
22 my $schema;
23
24 for my $storage_type (@storage_types) {
25   $schema = DBICTest::Schema->clone;
26
27   unless ($storage_type eq 'DBI::Sybase::ASE') { # autodetect
28     $schema->storage_type("::$storage_type");
29   }
30   $schema->connection($dsn, $user, $pass, {
31     on_connect_call => 'datetime_setup',
32   });
33
34   my $guard = scope_guard { cleanup($schema) };
35
36   $schema->storage->ensure_connected;
37
38   isa_ok( $schema->storage, "DBIx::Class::Storage::$storage_type" );
39
40   eval { $schema->storage->dbh->do("DROP TABLE track") };
41   $schema->storage->dbh->do(<<"SQL");
42 CREATE TABLE track (
43     trackid INT IDENTITY PRIMARY KEY,
44     cd INT NULL,
45     position INT NULL,
46     last_updated_at DATETIME NULL
47 )
48 SQL
49   eval { $schema->storage->dbh->do("DROP TABLE event_small_dt") };
50   $schema->storage->dbh->do(<<"SQL");
51 CREATE TABLE event_small_dt (
52     id INT IDENTITY PRIMARY KEY,
53     small_dt SMALLDATETIME NULL,
54 )
55 SQL
56
57 # coltype, column, source, pk, create_extra, datehash
58   my @dt_types = (
59     ['DATETIME',
60      'last_updated_at',
61      'Track',
62      'trackid',
63      { cd => 1 },
64      {
65       year => 2004,
66       month => 8,
67       day => 21,
68       hour => 14,
69       minute => 36,
70       second => 48,
71       nanosecond => 500000000,
72     }],
73     ['SMALLDATETIME', # minute precision
74      'small_dt',
75      'EventSmallDT',
76      'id',
77      {},
78      {
79       year => 2004,
80       month => 8,
81       day => 21,
82       hour => 14,
83       minute => 36,
84     }],
85   );
86
87   for my $dt_type (@dt_types) {
88     my ($type, $col, $source, $pk, $create_extra, $sample_dt) = @$dt_type;
89
90     ok(my $dt = DateTime->new($sample_dt));
91
92     my $row;
93     ok( $row = $schema->resultset($source)->create({
94           $col => $dt,
95           %$create_extra,
96         }));
97     ok( $row = $schema->resultset($source)
98       ->search({ $pk => $row->$pk }, { select => [$pk, $col] })
99       ->first
100     );
101     is( $row->$col, $dt, "$type roundtrip" );
102
103     cmp_ok( $row->$col->nanosecond, '==', $sample_dt->{nanosecond},
104       'DateTime fractional portion roundtrip' )
105       if exists $sample_dt->{nanosecond};
106
107     # Testing an ugly half-solution
108     #
109     # copy() uses get_columns()
110     #
111     # The values should survive a roundtrip also, but they don't
112     # because the Sybase ICDT setup is asymmetric
113     # One *has* to force an inflation/deflation cycle to make the
114     # values usable to the database
115     #
116     # This can be done by marking the columns as dirty, and there
117     # are tests for this already in t/inflate/serialize.t
118     #
119     # But even this isn't enough - one has to reload the RDBMS-formatted
120     # values once done, otherwise the copy is just as useless... sigh
121     #
122     # Adding the test here to validate the technique works
123     # UGH!
124     {
125       no warnings 'once';
126       local *DBICTest::BaseResult::copy = subname 'DBICTest::BaseResult::copy' => sub {
127         my $self = shift;
128
129         $self->make_column_dirty($_) for keys %{{ $self->get_inflated_columns }};
130
131         my $cp = $self->next::method(@_);
132
133         $cp->discard_changes({ columns => [ keys %{{ $cp->get_columns }} ] });
134       };
135       Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO;
136
137       my $cp = $row->copy;
138       ok( $cp->in_storage );
139       is( $cp->$col, $dt, "$type copy logical roundtrip" );
140
141       $cp->discard_changes({ select => [ $pk, $col ] });
142       is( $cp->$col, $dt, "$type copy server roundtrip" );
143     }
144
145     Class::C3->reinitialize if DBIx::Class::_ENV_::OLD_MRO;
146   }
147
148   # test a computed datetime column
149   eval { $schema->storage->dbh->do("DROP TABLE track") };
150   $schema->storage->dbh->do(<<"SQL");
151 CREATE TABLE track (
152     trackid INT IDENTITY PRIMARY KEY,
153     cd INT NULL,
154     position INT NULL,
155     title VARCHAR(100) NULL,
156     last_updated_on DATETIME NULL,
157     last_updated_at AS getdate(),
158 )
159 SQL
160
161   my $now = DateTime->now;
162   sleep 1;
163   my $new_row = $schema->resultset('Track')->create({});
164   $new_row->discard_changes;
165
166   lives_and {
167     cmp_ok (($new_row->last_updated_at - $now)->seconds, '>=', 1)
168   } 'getdate() computed column works';
169 }
170
171 done_testing;
172
173 # clean up our mess
174 sub cleanup {
175   my $schema = shift;
176   if (my $dbh = eval { $schema->storage->dbh }) {
177     $dbh->do('DROP TABLE track');
178     $dbh->do('DROP TABLE event_small_dt');
179   }
180 }