Switch the main dev branch back to 'master'
[dbsrgits/DBIx-Class.git] / t / prefetch / with_limit.t
1 # Test to ensure we get a consistent result set wether or not we use the
2 # prefetch option in combination rows (LIMIT).
3 use strict;
4 use warnings;
5
6 use Test::More;
7 use Test::Exception;
8 use lib qw(t/lib);
9 use DBICTest ':DiffSQL';
10 use DBIx::Class::SQLMaker::LimitDialects;
11
12 my $ROWS = DBIx::Class::SQLMaker::LimitDialects->__rows_bindtype;
13
14 my $schema = DBICTest->init_schema();
15
16
17 my $no_prefetch = $schema->resultset('Artist')->search(
18   [   # search deliberately contrived
19     { 'artwork.cd_id' => undef },
20     { 'tracks.title' => { '!=' => 'blah-blah-1234568' }}
21   ],
22   { rows => 3, join => { cds => [qw/artwork tracks/] },
23  }
24 );
25
26 my $use_prefetch = $no_prefetch->search(
27   {},
28   {
29     select => ['me.artistid', 'me.name'],
30     as => ['artistid', 'name'],
31     prefetch => 'cds',
32     order_by => { -desc => 'name' },
33   }
34 );
35
36 # add an extra +select to make sure it does not throw things off
37 # we also expect it to appear in both selectors, as we can not know
38 # for sure which part of the query it applies to (may be order_by,
39 # maybe something else)
40 #
41 # we use a reference to the same array in bind vals, because
42 # is_deeply picks up this difference too (not sure if bug or
43 # feature)
44 $use_prefetch = $use_prefetch->search({}, {
45   '+columns' => { monkeywrench => \[ 'me.artistid + ?', [ \ 'inTEger' => 1 ] ] },
46 });
47
48 my $bind_int_resolved = sub { [ { sqlt_datatype => 'inTEger' } => 1 ] };
49 my $bind_vc_resolved = sub { [
50   { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'tracks.title' }
51     => 'blah-blah-1234568'
52 ] };
53 is_same_sql_bind (
54   $use_prefetch->as_query,
55   '(
56     SELECT  me.artistid + ?,
57             me.artistid, me.name,
58             cds.cdid, cds.artist, cds.title, cds.year, cds.genreid, cds.single_track
59       FROM (
60         SELECT me.artistid + ?,
61                me.artistid, me.name
62           FROM artist me
63           LEFT JOIN cd cds
64             ON cds.artist = me.artistid
65           LEFT JOIN cd_artwork artwork
66             ON artwork.cd_id = cds.cdid
67           LEFT JOIN track tracks
68             ON tracks.cd = cds.cdid
69         WHERE   artwork.cd_id IS NULL
70              OR tracks.title != ?
71         GROUP BY me.artistid + ?, me.artistid, me.name
72         ORDER BY name DESC LIMIT ?
73       ) me
74       LEFT JOIN cd cds
75         ON cds.artist = me.artistid
76       LEFT JOIN cd_artwork artwork
77         ON artwork.cd_id = cds.cdid
78       LEFT JOIN track tracks
79         ON tracks.cd = cds.cdid
80     WHERE artwork.cd_id IS NULL
81        OR tracks.title != ?
82     ORDER BY name DESC
83   )',
84   [
85     $bind_int_resolved->(),  # outer select
86     $bind_int_resolved->(),  # inner select
87     $bind_vc_resolved->(), # inner where
88     $bind_int_resolved->(),  # inner group_by
89     [ $ROWS => 3 ],
90     $bind_vc_resolved->(), # outer where
91   ],
92   'Expected SQL on complex limited prefetch'
93 );
94
95 is($no_prefetch->count, $use_prefetch->count, '$no_prefetch->count == $use_prefetch->count');
96 is(
97   scalar ($no_prefetch->all),
98   scalar ($use_prefetch->all),
99   "Amount of returned rows is right"
100 );
101
102 my $artist_many_cds = $schema->resultset('Artist')->search ( {}, {
103   join => 'cds',
104   group_by => 'me.artistid',
105   having => \ 'count(cds.cdid) > 1',
106 })->first;
107
108
109 $no_prefetch = $schema->resultset('Artist')->search(
110   { artistid => $artist_many_cds->id },
111   { rows => 1 }
112 );
113
114 $use_prefetch = $no_prefetch->search ({}, { prefetch => 'cds' });
115
116 my $normal_artist = $no_prefetch->single;
117 my $prefetch_artist = $use_prefetch->find({ name => $artist_many_cds->name });
118 my $prefetch2_artist = $use_prefetch->first;
119
120 is(
121   $prefetch_artist->cds->count,
122   $normal_artist->cds->count,
123   "Count of child rel with prefetch + rows => 1 is right (find)"
124 );
125 is(
126   $prefetch2_artist->cds->count,
127   $normal_artist->cds->count,
128   "Count of child rel with prefetch + rows => 1 is right (first)"
129 );
130
131 is (
132   scalar ($prefetch_artist->cds->all),
133   scalar ($normal_artist->cds->all),
134   "Amount of child rel rows with prefetch + rows => 1 is right (find)"
135 );
136 is (
137   scalar ($prefetch2_artist->cds->all),
138   scalar ($normal_artist->cds->all),
139   "Amount of child rel rows with prefetch + rows => 1 is right (first)"
140 );
141
142 throws_ok (
143   sub { $use_prefetch->single },
144   qr/\Qsingle() can not be used on resultsets collapsing a has_many/,
145   'single() with multiprefetch is illegal',
146 );
147
148 throws_ok (
149   sub {
150     $use_prefetch->search(
151       {'tracks.title' => { '!=' => 'foo' }},
152       { order_by => \ 'some oddball literal sql', join => { cds => 'tracks' } }
153     )->next
154   }, qr/Unable to programatically derive a required group_by from the supplied order_by criteria/,
155 );
156
157 my $artist = $use_prefetch->search({'cds.title' => $artist_many_cds->cds->first->title })->next;
158 is($artist->cds->count, 1, "count on search limiting prefetched has_many");
159
160 # try with double limit
161 my $artist2 = $use_prefetch->search({'cds.title' => { '!=' => $artist_many_cds->cds->first->title } })->slice (0,0)->next;
162 is($artist2->cds->count, 2, "count on search limiting prefetched has_many");
163
164 # make sure 1:1 joins do not force a subquery (no point to exercise the optimizer, if at all available)
165 # get cd's that have any tracks and their artists
166 my $single_prefetch_rs = $schema->resultset ('CD')->search (
167   { 'me.year' => 2010, 'artist.name' => 'foo' },
168   { prefetch => ['tracks', 'artist'], rows => 15 },
169 );
170 is_same_sql_bind (
171   $single_prefetch_rs->as_query,
172   '(
173     SELECT
174         me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track,
175         tracks.trackid, tracks.cd, tracks.position, tracks.title, tracks.last_updated_on, tracks.last_updated_at,
176         artist.artistid, artist.name, artist.rank, artist.charfield
177       FROM (
178         SELECT
179             me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track
180           FROM cd me
181           JOIN artist artist ON artist.artistid = me.artist
182         WHERE ( ( artist.name = ? AND me.year = ? ) )
183         LIMIT ?
184       ) me
185       LEFT JOIN track tracks
186         ON tracks.cd = me.cdid
187       JOIN artist artist
188         ON artist.artistid = me.artist
189     WHERE ( ( artist.name = ? AND me.year = ? ) )
190   )',
191   [
192     [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'artist.name' } => 'foo' ],
193     [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'me.year' } => 2010 ],
194     [ $ROWS         => 15    ],
195     [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'artist.name' } => 'foo' ],
196     [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'me.year' } => 2010 ],
197   ],
198   'No grouping of non-multiplying resultsets',
199 );
200
201 my $many_one_many_rs = $schema->resultset('CD')->search({}, {
202   prefetch => { tracks => { lyrics => 'lyric_versions' } },
203   rows => 2,
204   order_by => ['lyrics.track_id'],
205 });
206
207 is_same_sql_bind(
208   $many_one_many_rs->as_query,
209   '(
210     SELECT  me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track,
211             tracks.trackid, tracks.cd, tracks.position, tracks.title, tracks.last_updated_on, tracks.last_updated_at,
212             lyrics.lyric_id, lyrics.track_id, lyric_versions.id, lyric_versions.lyric_id, lyric_versions.text
213       FROM (
214         SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track
215           FROM cd me
216           LEFT JOIN track tracks
217             ON tracks.cd = me.cdid
218           LEFT JOIN lyrics lyrics
219             ON lyrics.track_id = tracks.trackid
220         GROUP BY me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track
221         ORDER BY MIN(lyrics.track_id)
222         LIMIT ?
223       ) me
224       LEFT JOIN track tracks
225         ON tracks.cd = me.cdid
226       LEFT JOIN lyrics lyrics
227         ON lyrics.track_id = tracks.trackid
228       LEFT JOIN lyric_versions lyric_versions
229         ON lyric_versions.lyric_id = lyrics.lyric_id
230     ORDER BY lyrics.track_id
231   )',
232   [
233     [ { sqlt_datatype => 'integer' } => 2 ]
234   ],
235   'Correct SQL on indirectly multiplied orderer',
236 );
237
238 my $cond_on_multi_ord_by_single = $schema->resultset('CD')->search(
239   {
240     'tracks.position' => { '!=', 1 },
241   },
242   {
243     prefetch => [qw( tracks artist )],
244     order_by => 'artist.name',
245     rows => 1,
246   },
247 );
248
249 is_same_sql_bind(
250   $cond_on_multi_ord_by_single->as_query,
251   '(
252     SELECT  me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track,
253             tracks.trackid, tracks.cd, tracks.position, tracks.title, tracks.last_updated_on, tracks.last_updated_at,
254             artist.artistid, artist.name, artist.rank, artist.charfield
255       FROM (
256         SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track
257           FROM cd me
258           LEFT JOIN track tracks
259             ON tracks.cd = me.cdid
260           JOIN artist artist
261             ON artist.artistid = me.artist
262         WHERE tracks.position != ?
263         GROUP BY me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track, artist.name
264         ORDER BY artist.name
265         LIMIT ?
266       ) me
267       LEFT JOIN track tracks
268         ON tracks.cd = me.cdid
269       JOIN artist artist
270         ON artist.artistid = me.artist
271     WHERE tracks.position != ?
272     ORDER BY artist.name
273   )',
274   [
275     [ { dbic_colname => "tracks.position", sqlt_datatype => "int" }
276       => 1
277     ],
278     [ { sqlt_datatype => "integer" }
279       => 1
280     ],
281     [ { dbic_colname => "tracks.position", sqlt_datatype => "int" }
282       => 1
283     ],
284   ],
285   'Correct SQl on prefetch with limit of restricting multi ordered by a single'
286 );
287
288 done_testing;