a89897bd6772fd40405d16e988beb2d6c2cd4223
[dbsrgits/DBIx-Class.git] / t / resultset / rowparser_internals.t
1 use strict;
2 use warnings;
3
4 use Test::More;
5 use lib qw(t/lib);
6 use DBICTest;
7 use B::Deparse;
8
9 # globally set for the rest of test
10 # the rowparser maker does not order its hashes by default for the miniscule
11 # speed gain. But it does not disable sorting either - for this test
12 # everything will be ordered nicely, and the hash randomization of 5.18
13 # will not trip up anything
14 use Data::Dumper;
15 $Data::Dumper::Sortkeys = 1;
16
17 my $schema = DBICTest->init_schema(no_deploy => 1);
18 my $infmap = [qw/single_track.cd.artist.name year/];
19
20 is_same_src (
21   $schema->source ('CD')->_mk_row_parser({
22     inflate_map => $infmap,
23   }),
24   '$_ = [
25     { year => $_->[1] },
26     { single_track => [
27       undef,
28       { cd => [
29         undef,
30         { artist => [
31           { name  => $_->[0] },
32         ] },
33       ]},
34     ]},
35   ] for @{$_[0]}',
36   'Simple 1:1 descending non-collapsing parser',
37 );
38
39 $infmap = [qw/
40   single_track.cd.artist.cds.tracks.title
41   single_track.cd.artist.artistid
42   year
43   single_track.cd.artist.cds.cdid
44   title
45   artist
46 /];
47 is_same_src (
48   $schema->source ('CD')->_mk_row_parser({
49     inflate_map => $infmap,
50   }),
51   '$_ = [
52     { artist => $_->[5], title => $_->[4], year => $_->[2] },
53     { single_track => [
54       undef,
55       { cd => [
56         undef,
57         { artist => [
58           { artistid => $_->[1] },
59           { cds => [
60             { cdid => $_->[3] },
61             { tracks => [
62               { title => $_->[0] }
63             ] },
64           ] },
65         ] },
66       ] },
67     ] },
68   ] for @{$_[0]}',
69   '1:1 descending non-collapsing parser terminating with chained 1:M:M',
70 );
71
72 is_deeply (
73   ($schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} })),
74   {
75     -identifying_columns => [ 4, 5 ],
76
77     single_track => {
78       -identifying_columns => [ 1, 4, 5 ],
79       -is_optional => 1,
80       -is_single => 1,
81
82       cd => {
83         -identifying_columns => [ 1, 4, 5 ],
84         -is_single => 1,
85
86         artist => {
87           -identifying_columns => [ 1, 4, 5 ],
88           -is_single => 1,
89
90           cds => {
91             -identifying_columns => [ 1, 3, 4, 5 ],
92             -is_optional => 1,
93
94             tracks => {
95               -identifying_columns => [ 0, 1, 3, 4, 5 ],
96               -is_optional => 1,
97             },
98           },
99         },
100       },
101     },
102   },
103   'Correct collapse map for 1:1 descending chain terminating with chained 1:M:M'
104 );
105
106 is_same_src (
107   $schema->source ('CD')->_mk_row_parser({
108     inflate_map => $infmap,
109     collapse => 1,
110   }),
111   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
112
113     while ($cur_row_data = (
114       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
115         ||
116       ( $_[1] and $_[1]->() )
117     ) {
118
119       $cur_row_ids{$_} = defined $cur_row_data->[$_] ? $cur_row_data->[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
120         for (0, 1, 3, 4, 5);
121
122       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
123       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
124         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} );
125
126       # the rowdata itself for root node
127       $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} ||= [{ artist => $cur_row_data->[5], title => $cur_row_data->[4], year => $cur_row_data->[2] }];
128
129       # prefetch data of single_track (placed in root)
130       $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{single_track} ||= $collapse_idx[1]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}};
131
132       # prefetch data of cd (placed in single_track)
133       $collapse_idx[1]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}};
134
135       # prefetch data of artist ( placed in single_track->cd)
136       $collapse_idx[2]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}} ||= [{ artistid => $cur_row_data->[1] }];
137
138       # prefetch data of cds (if available)
139       push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cds}}, $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} ||= [{ cdid => $cur_row_data->[3] }]
140         unless $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}};
141
142       # prefetch data of tracks (if available)
143       push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{tracks}}, $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} ||= [{ title => $cur_row_data->[0] }]
144         unless $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}};
145
146       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}
147         if $is_new_res;
148     }
149     splice @{$_[0]}, $result_pos;
150   ',
151   'Same 1:1 descending terminating with chained 1:M:M but with collapse',
152 );
153
154 $infmap = [qw/
155   tracks.lyrics.existing_lyric_versions.text
156   existing_single_track.cd.artist.artistid
157   existing_single_track.cd.artist.cds.year
158   year
159   genreid
160   tracks.title
161   existing_single_track.cd.artist.cds.cdid
162   latest_cd
163   existing_single_track.cd.artist.cds.tracks.title
164   existing_single_track.cd.artist.cds.genreid
165   tracks.lyrics.existing_lyric_versions.lyric_id
166 /];
167
168 is_deeply (
169   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
170   {
171     -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
172
173     existing_single_track => {
174       -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
175       -is_single => 1,
176
177       cd => {
178         -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
179         -is_single => 1,
180
181         artist => {
182           -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
183           -is_single => 1,
184
185           cds => {
186             -identifying_columns => [ 1, 6 ], # existing_single_track.cd.artist.cds.cdid
187             -is_optional => 1,
188
189             tracks => {
190               -identifying_columns => [ 1, 6, 8 ], # existing_single_track.cd.artist.cds.cdid, existing_single_track.cd.artist.cds.tracks.title
191               -is_optional => 1,
192             }
193           }
194         }
195       }
196     },
197     tracks => {
198       -identifying_columns => [ 1, 5 ], # existing_single_track.cd.artist.artistid, tracks.title
199       -is_optional => 1,
200
201       lyrics => {
202         -identifying_columns => [ 1, 5, 10 ], # existing_single_track.cd.artist.artistid, tracks.title, tracks.lyrics.existing_lyric_versions.lyric_id
203         -is_single => 1,
204         -is_optional => 1,
205
206         existing_lyric_versions => {
207           -identifying_columns => [ 0, 1, 5, 10 ], # tracks.lyrics.existing_lyric_versions.text, existing_single_track.cd.artist.artistid, tracks.title, tracks.lyrics.existing_lyric_versions.lyric_id
208         },
209       },
210     }
211   },
212   'Correct collapse map constructed',
213 );
214
215 is_same_src (
216   $schema->source ('CD')->_mk_row_parser({
217     inflate_map => $infmap,
218     collapse => 1,
219   }),
220   ' my ($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0,0);
221
222     while ($cur_row_data = (
223       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
224         ||
225       ( $_[1] and $_[1]->() )
226     ) {
227
228       $cur_row_ids{$_} = defined $cur_row_data->[$_] ? $cur_row_data->[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
229         for (0, 1, 5, 6, 8, 10);
230
231       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
232       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
233         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{1}} );
234
235       $collapse_idx[0]{$cur_row_ids{1}} ||= [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
236
237       $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}};
238       $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}};
239       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}} ||= [{ artistid => $cur_row_data->[1] }];
240
241       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} ||= [{ cdid => $cur_row_data->[6], genreid => $cur_row_data->[9], year => $cur_row_data->[2] }]
242         unless $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}};
243
244       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} ||= [{ title => $cur_row_data->[8] }]
245         unless $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}};
246
247       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} ||= [{ title => $cur_row_data->[5] }]
248         unless $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}};
249
250       $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics} ||= $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}};
251
252       push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} ||= [{ lyric_id => $cur_row_data->[10], text => $cur_row_data->[0] }]
253         unless $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}};
254
255       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{1}}
256         if $is_new_res;
257     }
258
259     splice @{$_[0]}, $result_pos;
260   ',
261   'Multiple has_many on multiple branches torture test',
262 );
263
264 $infmap = [
265   'single_track.trackid',                   # (0) definitive link to root from 1:1:1:1:M:M chain
266   'year',                                   # (1) non-unique
267   'tracks.cd',                              # (2) \ together both uniqueness for second multirel
268   'tracks.title',                           # (3) / and definitive link back to root
269   'single_track.cd.artist.cds.cdid',        # (4) to give uniquiness to ...tracks.title below
270   'single_track.cd.artist.cds.year',        # (5) non-unique
271   'single_track.cd.artist.artistid',        # (6) uniqufies entire parental chain
272   'single_track.cd.artist.cds.genreid',     # (7) nullable
273   'single_track.cd.artist.cds.tracks.title',# (8) unique when combined with ...cds.cdid above
274 ];
275
276 is_deeply (
277   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
278   {
279     -identifying_columns => [],
280     -identifying_columns_variants => [
281       [ 0 ], [ 2 ],
282     ],
283     single_track => {
284       -identifying_columns => [ 0 ],
285       -is_optional => 1,
286       -is_single => 1,
287       cd => {
288         -identifying_columns => [ 0 ],
289         -is_single => 1,
290         artist => {
291           -identifying_columns => [ 0 ],
292           -is_single => 1,
293           cds => {
294             -identifying_columns => [ 0, 4 ],
295             -is_optional => 1,
296             tracks => {
297               -identifying_columns => [ 0, 4, 8 ],
298               -is_optional => 1,
299             }
300           }
301         }
302       }
303     },
304     tracks => {
305       -identifying_columns => [ 2, 3 ],
306       -is_optional => 1,
307     }
308   },
309   'Correct underdefined root collapse map constructed'
310 );
311
312 is_same_src (
313   $schema->source ('CD')->_mk_row_parser({
314     inflate_map => $infmap,
315     collapse => 1,
316   }),
317   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
318
319     while ($cur_row_data = (
320       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
321         ||
322       ( $_[1] and $_[1]->() )
323     ) {
324
325       $cur_row_ids{$_} = defined $$cur_row_data[$_] ? $$cur_row_data[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
326         for (0, 2, 3, 4, 8);
327
328       # cache expensive set of ops in a non-existent rowid slot
329       $cur_row_ids{10} = (
330         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_data->[0], q{} ))
331           or
332         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_data->[2], q{} ))
333           or
334         "\0$rows_pos\0"
335       );
336
337       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
338       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
339         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{10}} );
340
341       $collapse_idx[0]{$cur_row_ids{10}} ||= [{ year => $$cur_row_data[1] }];
342
343       $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track} ||= ($collapse_idx[1]{$cur_row_ids{0}} ||= [{ trackid => $$cur_row_data[0] }]);
344
345       $collapse_idx[1]{$cur_row_ids{0}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{0}};
346
347       $collapse_idx[2]{$cur_row_ids{0}}[1]{artist} ||= ($collapse_idx[3]{$cur_row_ids{0}} ||= [{ artistid => $$cur_row_data[6] }]);
348
349       push @{$collapse_idx[3]{$cur_row_ids{0}}[1]{cds}},
350           $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} ||= [{ cdid => $$cur_row_data[4], genreid => $$cur_row_data[7], year => $$cur_row_data[5] }]
351         unless $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}};
352
353       push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}},
354           $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} ||= [{ title => $$cur_row_data[8] }]
355         unless $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}};
356
357       push @{$collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}},
358           $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} ||= [{ cd => $$cur_row_data[2], title => $$cur_row_data[3] }]
359         unless $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}};
360
361       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{10}}
362         if $is_new_res;
363     }
364
365     splice @{$_[0]}, $result_pos;
366   ',
367   'Multiple has_many on multiple branches with underdefined root torture test',
368 );
369
370 done_testing;
371
372 my $deparser;
373 sub is_same_src {
374   $deparser ||= B::Deparse->new;
375   local $Test::Builder::Level = $Test::Builder::Level + 1;
376
377   my ($got, $expect) = map {
378     my $cref = eval "sub { $_ }" or do {
379       fail "Coderef does not compile!\n\n$@\n\n$_";
380       return undef;
381     };
382     $deparser->coderef2text($cref);
383   } @_[0,1];
384
385   is ($got, $expect, $_[2]||() )
386     or note ("Originals source:\n\n$_[0]\n\n$_[1]\n");
387 }
388