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