Add a self-explanatory *compile-time* $ENV{DBIC_SHUFFLE_UNORDERED_RESULTSETS}
[dbsrgits/DBIx-Class.git] / t / 88result_set_column.t
1 use strict;
2 use warnings;
3
4 use Test::More;
5 use Test::Warn;
6 use Test::Exception;
7
8 # MASSIVE FIXME - there is a hole in ::RSC / as_subselect_rs
9 # losing the order. Needs a rework/extract of the realiaser,
10 # and that's a whole another bag of dicks
11 BEGIN { $ENV{DBIC_SHUFFLE_UNORDERED_RESULTSETS} = 0 }
12
13 use lib qw(t/lib);
14 use DBICTest ':DiffSQL';
15
16 my $schema = DBICTest->init_schema();
17
18 my $rs = $schema->resultset("CD");
19
20 cmp_ok (
21   $rs->count,
22     '>',
23   $rs->search ({}, {columns => ['year'], distinct => 1})->count,
24   'At least one year is the same in rs'
25 );
26
27 my $rs_title = $rs->get_column('title');
28 my $rs_year = $rs->get_column('year');
29 my $max_year = $rs->get_column(\'MAX (year)');
30
31 my @all_titles = $rs_title->all;
32 cmp_ok(scalar @all_titles, '==', 5, "five titles returned");
33
34 my @nexted_titles;
35 while (my $r = $rs_title->next) {
36   push @nexted_titles, $r;
37 }
38
39 is_deeply (\@all_titles, \@nexted_titles, 'next works');
40
41 is_deeply( [ sort $rs_year->func('DISTINCT') ], [ 1997, 1998, 1999, 2001 ],  "wantarray context okay");
42 ok ($max_year->next == $rs_year->max, q/get_column (\'FUNC') ok/);
43
44 cmp_ok($rs_year->max, '==', 2001, "max okay for year");
45 is($rs_title->min, 'Caterwaulin\' Blues', "min okay for title");
46
47 cmp_ok($rs_year->sum, '==', 9996, "three artists returned");
48
49 my $rso_year = $rs->search({}, { order_by => 'cdid' })->get_column('year');
50 is($rso_year->next, 1999, "reset okay");
51
52 is($rso_year->first, 1999, "first okay");
53
54 warnings_exist (sub {
55   is($rso_year->single, 1999, "single okay");
56 }, qr/Query returned more than one row/, 'single warned');
57
58
59 # test distinct propagation
60 is_deeply (
61   [sort $rs->search ({}, { distinct => 1 })->get_column ('year')->all],
62   [sort $rs_year->func('distinct')],
63   'distinct => 1 is passed through properly',
64 );
65
66 # test illogical distinct
67 my $dist_rs = $rs->search ({}, {
68   columns => ['year'],
69   distinct => 1,
70   order_by => { -desc => [qw( cdid year )] },
71 });
72
73 is_same_sql_bind(
74   $dist_rs->as_query,
75   '(
76     SELECT me.year
77       FROM cd me
78     GROUP BY me.year
79     ORDER BY MAX(cdid) DESC, year DESC
80   )',
81   [],
82   'Correct SQL on external-ordered distinct',
83 );
84
85 is_same_sql_bind(
86   $dist_rs->count_rs->as_query,
87   '(
88     SELECT COUNT( * )
89       FROM (
90         SELECT me.year
91           FROM cd me
92         GROUP BY me.year
93       ) me
94   )',
95   [],
96   'Correct SQL on count of external-orderdd distinct',
97 );
98
99 is (
100   $dist_rs->count_rs->next,
101   4,
102   'Correct rs-count',
103 );
104
105 is (
106   $dist_rs->count,
107   4,
108   'Correct direct count',
109 );
110
111 # test +select/+as for single column
112 my $psrs = $schema->resultset('CD')->search({},
113     {
114         '+select'   => \'MAX(year)',
115         '+as'       => 'last_year'
116     }
117 );
118 lives_ok(sub { $psrs->get_column('last_year')->next }, '+select/+as additional column "last_year" present (scalar)');
119 dies_ok(sub { $psrs->get_column('noSuchColumn')->next }, '+select/+as nonexistent column throws exception');
120
121 # test +select/+as for overriding a column
122 $psrs = $schema->resultset('CD')->search({},
123     {
124         'select'   => \"'The Final Countdown'",
125         'as'       => 'title'
126     }
127 );
128 is($psrs->get_column('title')->next, 'The Final Countdown', '+select/+as overridden column "title"');
129
130
131 # test +select/+as for multiple columns
132 $psrs = $schema->resultset('CD')->search({},
133     {
134         '+select'   => [ \'LENGTH(title) AS title_length', 'title' ],
135         '+as'       => [ 'tlength', 'addedtitle' ]
136     }
137 );
138 lives_ok(sub { $psrs->get_column('tlength')->next }, '+select/+as multiple additional columns, "tlength" column present');
139 lives_ok(sub { $psrs->get_column('addedtitle')->next }, '+select/+as multiple additional columns, "addedtitle" column present');
140
141 # test that +select/+as specs do not leak
142 is_same_sql_bind (
143   $psrs->get_column('year')->as_query,
144   '(SELECT me.year FROM cd me)',
145   [],
146   'Correct SQL for get_column/as'
147 );
148
149 is_same_sql_bind (
150   $psrs->get_column('addedtitle')->as_query,
151   '(SELECT me.title FROM cd me)',
152   [],
153   'Correct SQL for get_column/+as col'
154 );
155
156 is_same_sql_bind (
157   $psrs->get_column('tlength')->as_query,
158   '(SELECT LENGTH(title) AS title_length FROM cd me)',
159   [],
160   'Correct SQL for get_column/+as func'
161 );
162
163 # test that order_by over a function forces a subquery
164 lives_ok ( sub {
165   is_deeply (
166     [ $psrs->search ({}, { order_by => { -desc => 'title_length' } })->get_column ('title')->all ],
167     [
168       "Generic Manufactured Singles",
169       "Come Be Depressed With Us",
170       "Caterwaulin' Blues",
171       "Spoonful of bees",
172       "Forkful of bees",
173     ],
174     'Subquery count induced by aliased ordering function',
175   );
176 });
177
178 # test for prefetch not leaking
179 {
180   my $rs = $schema->resultset("CD")->search({}, { prefetch => 'artist' });
181   my $rsc = $rs->get_column('year');
182   is( $rsc->{_parent_resultset}->{attrs}->{prefetch}, undef, 'prefetch wiped' );
183 }
184
185 # test sum()
186 is ($schema->resultset('BooksInLibrary')->get_column ('price')->sum, 125, 'Sum of a resultset works correctly');
187
188 # test sum over search_related
189 my $owner = $schema->resultset('Owners')->find ({ name => 'Newton' });
190 ok ($owner->books->count > 1, 'Owner Newton has multiple books');
191 is ($owner->search_related ('books')->get_column ('price')->sum, 60, 'Correctly calculated price of all owned books');
192
193
194 # make sure joined/prefetched get_column of a PK dtrt
195 $rs->reset;
196 my $j_rs = $rs->search ({}, { join => 'tracks' })->get_column ('cdid');
197 is_deeply (
198   [ sort $j_rs->all ],
199   [ sort map { my $c = $rs->next; ( ($c->id) x $c->tracks->count ) } (1 .. $rs->count) ],
200   'join properly explodes amount of rows from get_column',
201 );
202
203 $rs->reset;
204 my $p_rs = $rs->search ({}, { prefetch => 'tracks' })->get_column ('cdid');
205 is_deeply (
206   [ sort $p_rs->all ],
207   [ sort $rs->get_column ('cdid')->all ],
208   'prefetch properly collapses amount of rows from get_column',
209 );
210
211 $rs->reset;
212 my $pob_rs = $rs->search({}, {
213   select   => ['me.title', 'tracks.title'],
214   prefetch => 'tracks',
215   order_by => [{-asc => ['position']}],
216   group_by => ['me.title', 'tracks.title'],
217 });
218 is_same_sql_bind (
219   $pob_rs->get_column("me.title")->as_query,
220   '(SELECT me.title FROM (SELECT me.title, tracks.title FROM cd me LEFT JOIN track tracks ON tracks.cd = me.cdid GROUP BY me.title, tracks.title ORDER BY position ASC) me)',
221   [],
222   'Correct SQL for prefetch/order_by/group_by'
223 );
224
225 # test aggregate on a function (create an extra track on one cd)
226 {
227   my $tr_rs = $schema->resultset("Track");
228   $tr_rs->create({ cd => 2, title => 'dealbreaker' });
229
230   is(
231     $tr_rs->get_column('cd')->max,
232     5,
233     "Correct: Max cd in Track is 5"
234   );
235
236   my $track_counts_per_cd_via_group_by = $tr_rs->search({}, {
237     columns => [ 'cd', { cnt => { count => 'trackid', -as => 'cnt' } } ],
238     group_by => 'cd',
239   })->get_column('cnt');
240
241   is ($track_counts_per_cd_via_group_by->max, 4, 'Correct max tracks per cd');
242   is ($track_counts_per_cd_via_group_by->min, 3, 'Correct min tracks per cd');
243   is (
244     sprintf('%0.1f', $track_counts_per_cd_via_group_by->func('avg') ),
245     '3.2',
246     'Correct avg tracks per cd'
247   );
248 }
249
250 # test exotic scenarious (create a track-less cd)
251 # "How many CDs (not tracks) have been released per year where a given CD has at least one track and the artist isn't evancarroll?"
252 {
253
254   $schema->resultset('CD')->create({ artist => 1, title => 'dealbroker no tracks', year => 2001 });
255
256   my $yp1 = \[ 'year + ?', 1 ];
257
258   my $rs = $schema->resultset ('CD')->search (
259     { 'artist.name' => { '!=', 'evancarrol' }, 'tracks.trackid' => { '!=', undef } },
260     {
261       order_by => 'me.year',
262       join => [qw(artist tracks)],
263       columns => [
264         'year',
265         { cnt => { count => 'me.cdid' } },
266         {  year_plus_one => $yp1 },
267       ],
268     },
269   );
270
271   my $rstypes = {
272     'explicitly grouped' => $rs->search_rs({}, { group_by => [ 'year', $yp1 ] } ),
273     'implicitly grouped' => $rs->search_rs({}, { distinct => 1 }),
274   };
275
276   for my $type (keys %$rstypes) {
277     is ($rstypes->{$type}->count, 4, "correct cd count with $type column");
278
279     is_deeply (
280       [ $rstypes->{$type}->get_column ('year')->all ],
281       [qw(1997 1998 1999 2001)],
282       "Getting $type column works",
283     );
284   }
285
286   # Why do we test this - we want to make sure that the selector *will* actually make
287   # it to the group_by as per the distinct => 1 contract. Before 0.08251 this situation
288   # would silently drop the group_by entirely, likely ending up with nonsensival results
289   # With the current behavior the user will at least get a nice fat exception from the
290   # RDBMS (or maybe the RDBMS will even decide to handle the situation sensibly...)
291   for (
292     [ cnt => 'COUNT( me.cdid )' ],
293     [ year_plus_one => 'year + ?' => [ {} => 1 ] ],
294   ) {
295     my ($col, $sel_grp_sql, @sel_grp_bind) = @$_;
296
297     warnings_exist { is_same_sql_bind(
298       $rstypes->{'implicitly grouped'}->get_column($col)->as_query,
299       "(
300         SELECT $sel_grp_sql
301           FROM cd me
302           JOIN artist artist
303             ON artist.artistid = me.artist
304           LEFT JOIN track tracks
305             ON tracks.cd = me.cdid
306         WHERE artist.name != ? AND tracks.trackid IS NOT NULL
307         GROUP BY $sel_grp_sql
308         ORDER BY MIN(me.year)
309       )",
310       [
311         @sel_grp_bind,
312         [ { dbic_colname => 'artist.name', sqlt_datatype => 'varchar', sqlt_size => 100 }
313           => 'evancarrol' ],
314         @sel_grp_bind,
315       ],
316       'Expected (though nonsensical) SQL generated on rscol-with-distinct-over-function',
317     ) } qr/
318       \QUse of distinct => 1 while selecting anything other than a column \E
319       \Qdeclared on the primary ResultSource is deprecated (you selected '$col')\E
320     /x, 'deprecation warning';
321   }
322
323   {
324     local $TODO = 'multiplying join leaks through to the count aggregate... this may never actually work';
325     is_deeply (
326       [ $rstypes->{'explicitly grouped'}->get_column ('cnt')->all ],
327       [qw(1 1 1 2)],
328       "Get aggregate over group works",
329     );
330   }
331 }
332
333 done_testing;