Institute a central "load this first in testing" package
[dbsrgits/DBIx-Class.git] / t / row / filter_column.t
1 BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) }
2
3 use strict;
4 use warnings;
5
6 use Test::More;
7 use Test::Exception;
8
9 use DBICTest;
10
11 my $from_storage_ran = 0;
12 my $to_storage_ran = 0;
13 my $schema = DBICTest->init_schema( no_populate => 1 );
14 DBICTest::Schema::Artist->load_components(qw(FilterColumn InflateColumn));
15 DBICTest::Schema::Artist->filter_column(charfield => {
16   filter_from_storage => sub { $from_storage_ran++; defined $_[1] ? $_[1] * 2 : undef },
17   filter_to_storage   => sub { $to_storage_ran++; defined $_[1] ? $_[1] / 2 : undef },
18 });
19 Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO;
20
21 my $artist = $schema->resultset('Artist')->create( { charfield => 20 } );
22
23 # this should be using the cursor directly, no inflation/processing of any sort
24 my ($raw_db_charfield) = $schema->resultset('Artist')
25                              ->search ($artist->ident_condition)
26                                ->get_column('charfield')
27                                 ->_resultset
28                                  ->cursor
29                                   ->next;
30
31 is ($raw_db_charfield, 10, 'INSERT: correctly unfiltered on insertion');
32
33 for my $reloaded (0, 1) {
34   my $test = $reloaded ? 'reloaded' : 'stored';
35   $artist->discard_changes if $reloaded;
36
37   is( $artist->charfield , 20, "got $test filtered charfield" );
38 }
39
40 $artist->update;
41 $artist->discard_changes;
42 is( $artist->charfield , 20, "got filtered charfield" );
43
44 $artist->update ({ charfield => 40 });
45 ($raw_db_charfield) = $schema->resultset('Artist')
46                              ->search ($artist->ident_condition)
47                                ->get_column('charfield')
48                                 ->_resultset
49                                  ->cursor
50                                   ->next;
51 is ($raw_db_charfield, 20, 'UPDATE: correctly unflitered on update');
52
53 $artist->discard_changes;
54 $artist->charfield(40);
55 ok( !$artist->is_column_changed('charfield'), 'column is not dirty after setting the same value' );
56
57 MC: {
58    my $cd = $schema->resultset('CD')->create({
59       artist => { charfield => 20 },
60       title => 'fun time city!',
61       year => 'forevertime',
62    });
63    ($raw_db_charfield) = $schema->resultset('Artist')
64                                 ->search ($cd->artist->ident_condition)
65                                   ->get_column('charfield')
66                                    ->_resultset
67                                     ->cursor
68                                      ->next;
69
70    is $raw_db_charfield, 10, 'artist charfield gets correctly unfiltered w/ MC';
71    is $cd->artist->charfield, 20, 'artist charfield gets correctly filtered w/ MC';
72 }
73
74 CACHE_TEST: {
75   my $expected_from = $from_storage_ran;
76   my $expected_to   = $to_storage_ran;
77
78   # ensure we are creating a fresh obj
79   $artist = $schema->resultset('Artist')->single($artist->ident_condition);
80
81   is $from_storage_ran, $expected_from, 'from has not run yet';
82   is $to_storage_ran, $expected_to, 'to has not run yet';
83
84   $artist->charfield;
85   cmp_ok (
86     $artist->get_filtered_column('charfield'),
87       '!=',
88     $artist->get_column('charfield'),
89     'filter/unfilter differ'
90   );
91   is $from_storage_ran, ++$expected_from, 'from ran once, therefor caches';
92   is $to_storage_ran, $expected_to,  'to did not run';
93
94   $artist->charfield(6);
95   is $from_storage_ran, $expected_from, 'from did not run';
96   is $to_storage_ran, ++$expected_to,  'to ran once';
97
98   ok ($artist->is_column_changed ('charfield'), 'Column marked as dirty');
99
100   $artist->charfield;
101   is $from_storage_ran, $expected_from, 'from did not run';
102   is $to_storage_ran, $expected_to,  'to did not run';
103
104   $artist->update;
105
106   $artist->set_column(charfield => 3);
107   ok (! $artist->is_column_changed ('charfield'), 'Column not marked as dirty on same set_column value');
108   is ($artist->charfield, '6', 'Column set properly (cache blown)');
109   is $from_storage_ran, ++$expected_from, 'from ran once (set_column blew cache)';
110   is $to_storage_ran, $expected_to,  'to did not run';
111
112   $artist->charfield(6);
113   ok (! $artist->is_column_changed ('charfield'), 'Column not marked as dirty on same accessor-set value');
114   is ($artist->charfield, '6', 'Column set properly');
115   is $from_storage_ran, $expected_from, 'from did not run';
116   is $to_storage_ran, ++$expected_to,  'to did run once (call in to set_column)';
117
118   $artist->store_column(charfield => 4);
119   ok (! $artist->is_column_changed ('charfield'), 'Column not marked as dirty on differing store_column value');
120   is ($artist->charfield, '8', 'Cache properly blown');
121   is $from_storage_ran, ++$expected_from, 'from did not run';
122   is $to_storage_ran, $expected_to,  'to did not run';
123
124   $artist->update({ charfield => undef });
125   is $from_storage_ran, $expected_from, 'from did not run';
126   is $to_storage_ran, ++$expected_to,  'to did run';
127
128   $artist->discard_changes;
129   is ( $artist->get_column('charfield'), undef, 'Got back null' );
130   is ( $artist->charfield, undef, 'Got back null through filter' );
131
132   is $from_storage_ran, ++$expected_from, 'from did run';
133   is $to_storage_ran, $expected_to,  'to did not run';
134
135 }
136
137 # test in-memory operations
138 for my $artist_maker (
139   sub { $schema->resultset('Artist')->new({ charfield => 42 }) },
140   sub { my $art = $schema->resultset('Artist')->new({}); $art->charfield(42); $art },
141 ) {
142
143   my $expected_from = $from_storage_ran;
144   my $expected_to   = $to_storage_ran;
145
146   my $artist = $artist_maker->();
147
148   is $from_storage_ran, $expected_from, 'from has not run yet';
149   is $to_storage_ran, $expected_to, 'to has not run yet';
150
151   ok( ! $artist->has_column_loaded('artistid'), 'pk not loaded' );
152   ok( $artist->has_column_loaded('charfield'), 'Filtered column marked as loaded under new' );
153   is( $artist->charfield, 42, 'Proper unfiltered value' );
154   is( $artist->get_column('charfield'), 21, 'Proper filtered value' );
155 }
156
157 # test literals
158 for my $v ( \ '16', \[ '?', '16' ] ) {
159   my $rs = $schema->resultset('Artist');
160   $rs->delete;
161
162   my $art = $rs->new({ charfield => 10 });
163   $art->charfield($v);
164
165   is_deeply( $art->charfield, $v);
166   is_deeply( $art->get_filtered_column("charfield"), $v);
167   is_deeply( $art->get_column("charfield"), $v);
168
169   $art->insert;
170   $art->discard_changes;
171
172   is ($art->get_column("charfield"), 16, "Literal inserted into database properly");
173   is ($art->charfield, 32, "filtering still works");
174
175   $art->update({ charfield => $v });
176
177   is_deeply( $art->charfield, $v);
178   is_deeply( $art->get_filtered_column("charfield"), $v);
179   is_deeply( $art->get_column("charfield"), $v);
180
181   $art->discard_changes;
182
183   is ($art->get_column("charfield"), 16, "Literal inserted into database properly");
184   is ($art->charfield, 32, "filtering still works");
185 }
186
187 IC_DIE: {
188   throws_ok {
189      DBICTest::Schema::Artist->inflate_column(charfield =>
190         { inflate => sub {}, deflate => sub {} }
191      );
192   } qr/InflateColumn can not be used on a column with a declared FilterColumn filter/, q(Can't inflate column after filter column);
193
194   DBICTest::Schema::Artist->inflate_column(name =>
195      { inflate => sub {}, deflate => sub {} }
196   );
197
198   throws_ok {
199      DBICTest::Schema::Artist->filter_column(name => {
200         filter_to_storage => sub {},
201         filter_from_storage => sub {}
202      });
203   } qr/FilterColumn can not be used on a column with a declared InflateColumn inflator/, q(Can't filter column after inflate column);
204 }
205
206 # test when we do not set both filter_from_storage/filter_to_storage
207 DBICTest::Schema::Artist->filter_column(charfield => {
208   filter_to_storage => sub { $to_storage_ran++; $_[1] },
209 });
210 Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO;
211
212 ASYMMETRIC_TO_TEST: {
213   # initialise value
214   $artist->charfield(20);
215   $artist->update;
216
217   my $expected_from = $from_storage_ran;
218   my $expected_to   = $to_storage_ran;
219
220   $artist->charfield(10);
221   ok ($artist->is_column_changed ('charfield'), 'Column marked as dirty on accessor-set value');
222   is ($artist->charfield, '10', 'Column set properly');
223   is $from_storage_ran, $expected_from, 'from did not run';
224   is $to_storage_ran, ++$expected_to,  'to did run';
225
226   $artist->discard_changes;
227
228   is ($artist->charfield, '20', 'Column set properly');
229   is $from_storage_ran, $expected_from, 'from did not run';
230   is $to_storage_ran, $expected_to,  'to did not run';
231 }
232
233 DBICTest::Schema::Artist->filter_column(charfield => {
234   filter_from_storage => sub { $from_storage_ran++; $_[1] },
235 });
236 Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO;
237
238 ASYMMETRIC_FROM_TEST: {
239   # initialise value
240   $artist->charfield(23);
241   $artist->update;
242
243   my $expected_from = $from_storage_ran;
244   my $expected_to   = $to_storage_ran;
245
246   $artist->charfield(13);
247   ok ($artist->is_column_changed ('charfield'), 'Column marked as dirty on accessor-set value');
248   is ($artist->charfield, '13', 'Column set properly');
249   is $from_storage_ran, $expected_from, 'from did not run';
250   is $to_storage_ran, $expected_to,  'to did not run';
251
252   $artist->discard_changes;
253
254   is ($artist->charfield, '23', 'Column set properly');
255   is $from_storage_ran, ++$expected_from, 'from did run';
256   is $to_storage_ran, $expected_to,  'to did not run';
257 }
258
259 throws_ok { DBICTest::Schema::Artist->filter_column( charfield => {} ) }
260   qr/\QAn invocation of filter_column() must specify either a filter_from_storage or filter_to_storage/,
261   'Correctly throws exception for empty attributes'
262 ;
263
264 FC_ON_PK_TEST: {
265   # there are cases in the wild that autovivify stuff deep in the
266   # colinfo guts. While this is insane, there is no alternative
267   # so at leats make sure it keeps working...
268
269   $schema->source('Artist')->column_info('artistid')->{_filter_info} ||= {};
270
271   for my $key ('', 'primary') {
272     lives_ok {
273       $schema->resultset('Artist')->find_or_create({ artistid => 42 }, { $key ? ( key => $key ) : () });
274     };
275   }
276
277
278   DBICTest::Schema::Artist->filter_column(artistid => {
279     filter_to_storage => sub { $_[1] * 100 },
280     filter_from_storage => sub { $_[1] - 100 },
281   });
282
283   for my $key ('', 'primary') {
284     throws_ok {
285       $schema->resultset('Artist')->find_or_create({ artistid => 42 }, { $key ? ( key => $key ) : () });
286     } qr/\QUnable to satisfy requested constraint 'primary', FilterColumn values not usable for column(s): 'artistid'/;
287   }
288 }
289
290 done_testing;