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