Introduce GOVERNANCE document and empty RESOLUTIONS file.
[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   ok ! $artist->is_changed, 'object clean';
136   is_deeply
137     { $artist->get_dirty_columns },
138     {},
139     'dirty columns as expected',
140   ;
141
142   is $from_storage_ran, $expected_from, 'from did not run';
143   is $to_storage_ran, $expected_to,  'to did not run';
144
145   $artist->charfield(42);
146
147   is $from_storage_ran, $expected_from, 'from did not run';
148   is $to_storage_ran, ++$expected_to,  'to ran once, determining dirtyness';
149
150   is $artist->charfield, 42, 'setting once works';
151   ok $artist->is_column_changed('charfield'), 'column changed';
152   ok $artist->is_changed, 'object changed';
153   is_deeply
154     { $artist->get_dirty_columns },
155     { charfield => 21 },
156     'dirty columns as expected',
157   ;
158
159   is $from_storage_ran, $expected_from, 'from did not run';
160   is $to_storage_ran, $expected_to,  'to did not run';
161
162   $artist->charfield(66);
163   is $artist->charfield, 66, 'setting twice works';
164   ok $artist->is_column_changed('charfield'), 'column changed';
165   ok $artist->is_changed, 'object changed';
166
167   is $from_storage_ran, $expected_from, 'from did not run';
168   is $to_storage_ran, $expected_to,  'to did not run a second time on dirty column';
169
170   is_deeply
171     { $artist->get_dirty_columns },
172     { charfield => 33 },
173     'dirty columns as expected',
174   ;
175   is $from_storage_ran, $expected_from, 'from did not run';
176   is $to_storage_ran, ++$expected_to,  'to did run producing a new dirty_columns set';
177
178   is_deeply
179     { $artist->get_dirty_columns },
180     { charfield => 33 },
181     'dirty columns still as expected',
182   ;
183   is $from_storage_ran, $expected_from, 'from did not run';
184   is $to_storage_ran, $expected_to,  'to did not run on re-invoked get_dirty_columns';
185
186   $artist->update;
187   is $artist->charfield, 66, 'value still there';
188
189   is $from_storage_ran, $expected_from, 'from did not run';
190   is $to_storage_ran, $expected_to, 'to did not run ';
191
192   $artist->discard_changes;
193
194   is $from_storage_ran, $expected_from, 'from did not run after discard_changes';
195   is $to_storage_ran, $expected_to, 'to did not run after discard_changes';
196
197   is $artist->charfield, 66, 'value still there post reload';
198
199   is $from_storage_ran, ++$expected_from, 'from did run';
200   is $to_storage_ran, $expected_to,  'to did not run';
201 }
202
203 # test in-memory operations
204 for my $artist_maker (
205   sub { $schema->resultset('Artist')->new({ charfield => 42 }) },
206   sub { my $art = $schema->resultset('Artist')->new({}); $art->charfield(42); $art },
207 ) {
208   $schema->resultset('Artist')->delete;
209
210   my $expected_from = $from_storage_ran;
211   my $expected_to   = $to_storage_ran;
212
213   my $artist = $artist_maker->();
214
215   is $from_storage_ran, $expected_from, 'from has not run yet';
216   is $to_storage_ran, $expected_to, 'to has not run yet';
217
218   ok( ! $artist->has_column_loaded('artistid'), 'pk not loaded' );
219   ok( $artist->has_column_loaded('charfield'), 'Filtered column marked as loaded under new' );
220   is( $artist->charfield, 42, 'Proper unfiltered value' );
221   is( $artist->get_column('charfield'), 21, 'Proper filtered value' );
222
223   $artist->insert;
224   ($raw_db_charfield) = $schema->resultset('Artist')
225                                 ->search ($artist->ident_condition)
226                                  ->get_column('charfield')
227                                   ->next;
228
229   is $raw_db_charfield, 21, 'Proper value in database';
230 }
231
232 # test literals
233 for my $v ( \ '16', \[ '?', '16' ] ) {
234   my $rs = $schema->resultset('Artist');
235   $rs->delete;
236
237   my $art = $rs->new({ charfield => 10 });
238   $art->charfield($v);
239
240   is_deeply( $art->charfield, $v);
241   is_deeply( $art->get_filtered_column("charfield"), $v);
242   is_deeply( $art->get_column("charfield"), $v);
243
244   $art->insert;
245   $art->discard_changes;
246
247   is ($art->get_column("charfield"), 16, "Literal inserted into database properly");
248   is ($art->charfield, 32, "filtering still works");
249
250   $art->update({ charfield => $v });
251
252   is_deeply( $art->charfield, $v);
253   is_deeply( $art->get_filtered_column("charfield"), $v);
254   is_deeply( $art->get_column("charfield"), $v);
255
256   $art->discard_changes;
257
258   is ($art->get_column("charfield"), 16, "Literal inserted into database properly");
259   is ($art->charfield, 32, "filtering still works");
260 }
261
262 IC_DIE: {
263   throws_ok {
264      DBICTest::Schema::Artist->inflate_column(charfield =>
265         { inflate => sub {}, deflate => sub {} }
266      );
267   } qr/InflateColumn can not be used on a column with a declared FilterColumn filter/, q(Can't inflate column after filter column);
268
269   DBICTest::Schema::Artist->inflate_column(name =>
270      { inflate => sub {}, deflate => sub {} }
271   );
272
273   throws_ok {
274      DBICTest::Schema::Artist->filter_column(name => {
275         filter_to_storage => sub {},
276         filter_from_storage => sub {}
277      });
278   } qr/FilterColumn can not be used on a column with a declared InflateColumn inflator/, q(Can't filter column after inflate column);
279 }
280
281 # test when we do not set both filter_from_storage/filter_to_storage
282 DBICTest::Schema::Artist->filter_column(charfield => {
283   filter_to_storage => sub { $to_storage_ran++; $_[1] },
284 });
285 Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO;
286
287 ASYMMETRIC_TO_TEST: {
288   # initialise value
289   $artist->charfield(20);
290   $artist->update;
291
292   my $expected_from = $from_storage_ran;
293   my $expected_to   = $to_storage_ran;
294
295   $artist->charfield(10);
296   ok ($artist->is_column_changed ('charfield'), 'Column marked as dirty on accessor-set value');
297   is ($artist->charfield, '10', 'Column set properly');
298   is $from_storage_ran, $expected_from, 'from did not run';
299   is $to_storage_ran, ++$expected_to,  'to did run';
300
301   $artist->discard_changes;
302
303   is ($artist->charfield, '20', 'Column set properly');
304   is $from_storage_ran, $expected_from, 'from did not run';
305   is $to_storage_ran, $expected_to,  'to did not run';
306 }
307
308 DBICTest::Schema::Artist->filter_column(charfield => {
309   filter_from_storage => sub { $from_storage_ran++; $_[1] },
310 });
311 Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO;
312
313 ASYMMETRIC_FROM_TEST: {
314   # initialise value
315   $artist->charfield(23);
316   $artist->update;
317
318   my $expected_from = $from_storage_ran;
319   my $expected_to   = $to_storage_ran;
320
321   $artist->charfield(13);
322   ok ($artist->is_column_changed ('charfield'), 'Column marked as dirty on accessor-set value');
323   is ($artist->charfield, '13', 'Column set properly');
324   is $from_storage_ran, $expected_from, 'from did not run';
325   is $to_storage_ran, $expected_to,  'to did not run';
326
327   $artist->discard_changes;
328
329   is ($artist->charfield, '23', 'Column set properly');
330   is $from_storage_ran, ++$expected_from, 'from did run';
331   is $to_storage_ran, $expected_to,  'to did not run';
332 }
333
334 throws_ok { DBICTest::Schema::Artist->filter_column( charfield => {} ) }
335   qr/\QAn invocation of filter_column() must specify either a filter_from_storage or filter_to_storage/,
336   'Correctly throws exception for empty attributes'
337 ;
338
339 FC_ON_PK_TEST: {
340   # there are cases in the wild that autovivify stuff deep in the
341   # colinfo guts. While this is insane, there is no alternative
342   # so at leats make sure it keeps working...
343
344   $schema->source('Artist')->column_info('artistid')->{_filter_info} ||= {};
345
346   for my $key ('', 'primary') {
347     lives_ok {
348       $schema->resultset('Artist')->find_or_create({ artistid => 42 }, { $key ? ( key => $key ) : () });
349     };
350   }
351
352
353   DBICTest::Schema::Artist->filter_column(artistid => {
354     filter_to_storage => sub { $_[1] * 100 },
355     filter_from_storage => sub { $_[1] - 100 },
356   });
357
358   for my $key ('', 'primary') {
359     throws_ok {
360       $schema->resultset('Artist')->find_or_create({ artistid => 42 }, { $key ? ( key => $key ) : () });
361     } qr/\QUnable to satisfy requested constraint 'primary', FilterColumn values not usable for column(s): 'artistid'/;
362   }
363 }
364
365 done_testing;