Fatalize warnings within the compiled row parsers
[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/
19   single_track.cd.artist.name
20   year
21 /];
22
23 is_same_src (
24   $schema->source ('CD')->_mk_row_parser({
25     inflate_map => $infmap,
26   }),
27   '$_ = [
28     { year => $_->[1] },
29     { single_track => ( ! defined( $_->[0]) )
30       ? bless( [
31         undef,
32         { cd => [
33           undef,
34           { artist => [
35             { name  => $_->[0] },
36           ] },
37         ] },
38       ], __NBC__ )
39       : [
40         undef,
41         { cd => [
42           undef,
43           { artist => [
44             { name  => $_->[0] },
45           ] },
46         ] },
47       ]
48     },
49   ] for @{$_[0]}',
50   'Simple 1:1 descending non-collapsing parser',
51 );
52
53 $infmap = [qw/
54   single_track.cd.artist.cds.tracks.title
55   single_track.cd.artist.artistid
56   year
57   single_track.cd.artist.cds.cdid
58   title
59   artist
60 /];
61
62 is_same_src (
63   $schema->source ('CD')->_mk_row_parser({
64     inflate_map => $infmap,
65   }),
66   '$_ = [
67     { artist => $_->[5], title => $_->[4], year => $_->[2] },
68     {
69       single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
70         ? bless( [
71           undef,
72           {
73             cd => [
74               undef,
75               {
76                 artist => [
77                   { artistid => $_->[1] },
78                   {
79                     cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
80                       ? bless ([
81                         { cdid => $_->[3] },
82                         {
83                           tracks => ( ! defined $_->[0] )
84                             ? bless ( [{ title => $_->[0] }], __NBC__ )
85                             : [{ title => $_->[0] }]
86                         }
87                       ], __NBC__)
88                       : [
89                         { cdid => $_->[3] },
90                         {
91                           tracks => ( ! defined $_->[0] )
92                             ? bless ( [{ title => $_->[0] }], __NBC__ )
93                             : [{ title => $_->[0] }]
94                         }
95                       ]
96                   }
97                 ]
98               }
99             ]
100           }
101         ], __NBC__)
102         : [
103           undef,
104           {
105             cd => [
106               undef,
107               {
108                 artist => [
109                   { artistid => $_->[1] },
110                   {
111                     cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
112                       ? bless ([
113                         { cdid => $_->[3] },
114                         {
115                           tracks => ( ! defined $_->[0] )
116                             ? bless ( [{ title => $_->[0] }], __NBC__ )
117                             : [{ title => $_->[0] }]
118                         }
119                       ], __NBC__)
120                       : [
121                         { cdid => $_->[3] },
122                         {
123                           tracks => ( ! defined $_->[0] )
124                             ? bless ( [{ title => $_->[0] }], __NBC__ )
125                             : [{ title => $_->[0] }]
126                         }
127                       ]
128                   }
129                 ]
130               }
131             ]
132           }
133         ]
134     }
135   ] for @{$_[0]}',
136   '1:1 descending non-collapsing parser terminating with chained 1:M:M',
137 );
138
139 is_same_src (
140   $schema->source ('CD')->_mk_row_parser({
141     hri_style => 1,
142     inflate_map => $infmap,
143   }),
144   '$_ = {
145       artist => $_->[5], title => $_->[4], year => $_->[2],
146
147       ( single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
148         ? undef
149         : {
150             cd =>
151               {
152                 artist => {
153                     artistid => $_->[1],
154                     ( cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
155                       ? undef
156                       : {
157                           cdid => $_->[3],
158                           ( tracks => ( ! defined $_->[0] )
159                             ? undef
160                             : { title => $_->[0] }
161                           )
162                         }
163                     )
164                   }
165               }
166           }
167       )
168     } for @{$_[0]}',
169   '1:1 descending non-collapsing HRI-direct parser terminating with chained 1:M:M',
170 );
171
172
173
174 is_deeply (
175   ($schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} })),
176   {
177     -identifying_columns => [ 4, 5 ],
178
179     single_track => {
180       -identifying_columns => [ 1, 4, 5 ],
181       -is_optional => 1,
182       -is_single => 1,
183
184       cd => {
185         -identifying_columns => [ 1, 4, 5 ],
186         -is_single => 1,
187
188         artist => {
189           -identifying_columns => [ 1, 4, 5 ],
190           -is_single => 1,
191
192           cds => {
193             -identifying_columns => [ 1, 3, 4, 5 ],
194             -is_optional => 1,
195
196             tracks => {
197               -identifying_columns => [ 0, 1, 3, 4, 5 ],
198               -is_optional => 1,
199             },
200           },
201         },
202       },
203     },
204   },
205   'Correct collapse map for 1:1 descending chain terminating with chained 1:M:M'
206 );
207
208 is_same_src (
209   $schema->source ('CD')->_mk_row_parser({
210     inflate_map => $infmap,
211     collapse => 1,
212   }),
213   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
214
215     while ($cur_row_data = (
216       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
217         ||
218       ( $_[1] and $_[1]->() )
219     ) {
220
221       $cur_row_ids{$_} = defined $cur_row_data->[$_] ? $cur_row_data->[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
222         for (0, 1, 3, 4, 5);
223
224       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
225       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
226         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} );
227
228       # the rowdata itself for root node
229       $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] }];
230
231       # prefetch data of single_track (placed in root)
232       $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}} ||= [];
233       defined($cur_row_data->[1]) or bless( $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{single_track}, __NBC__ );
234
235       # prefetch data of cd (placed in single_track)
236       $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}} ||= [];
237
238       # prefetch data of artist ( placed in single_track->cd)
239       $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] }];
240
241       # prefetch data of cds (if available)
242       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
243         and
244       push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cds}}, (
245         $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = [{ cdid => $cur_row_data->[3] }]
246       );
247       defined($cur_row_data->[3]) or bless( $collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cds}, __NBC__ );
248
249       # prefetch data of tracks (if available)
250       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
251         and
252       push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{tracks}}, (
253         $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] }]
254       );
255       defined($cur_row_data->[0]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{tracks}, __NBC__ );
256
257       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}
258         if $is_new_res;
259     }
260     splice @{$_[0]}, $result_pos;
261   ',
262   'Same 1:1 descending terminating with chained 1:M:M but with collapse',
263 );
264
265 is_same_src (
266   $schema->source ('CD')->_mk_row_parser({
267     inflate_map => $infmap,
268     collapse => 1,
269     hri_style => 1,
270   }),
271   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
272
273     while ($cur_row_data = (
274       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
275         ||
276       ( $_[1] and $_[1]->() )
277     ) {
278
279       $cur_row_ids{$_} = defined $cur_row_data->[$_] ? $cur_row_data->[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
280         for (0, 1, 3, 4, 5);
281
282       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
283       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
284         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} );
285
286       # the rowdata itself for root node
287       $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] };
288
289       # prefetch data of single_track (placed in root)
290       $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}{single_track} ||= $collapse_idx[1]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}
291         if defined $cur_row_data->[1];
292       $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}{single_track} ||= undef;
293
294       # prefetch data of cd (placed in single_track)
295       $collapse_idx[1]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}{cd} ||= $collapse_idx[2]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}};
296
297       # prefetch data of artist ( placed in single_track->cd)
298       $collapse_idx[2]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}{artist} ||= $collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}} ||= { artistid => $cur_row_data->[1] };
299
300       # prefetch data of cds (if available)
301       ( defined $cur_row_data->[3] )
302         and
303       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
304         and
305       push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}{cds}}, (
306         $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = { cdid => $cur_row_data->[3] }
307       );
308       $collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}{cds} ||= [];
309
310       # prefetch data of tracks (if available)
311       ( defined $cur_row_data->[0] )
312         and
313       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
314         and
315       push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}{tracks}}, (
316         $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] }
317       );
318       $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}{tracks} ||= [];
319
320       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}
321         if $is_new_res;
322     }
323     splice @{$_[0]}, $result_pos;
324   ',
325   'Same 1:1 descending terminating with chained 1:M:M but with collapse, HRI-direct',
326 );
327
328 $infmap = [qw/
329   tracks.lyrics.existing_lyric_versions.text
330   existing_single_track.cd.artist.artistid
331   existing_single_track.cd.artist.cds.year
332   year
333   genreid
334   tracks.title
335   existing_single_track.cd.artist.cds.cdid
336   latest_cd
337   existing_single_track.cd.artist.cds.tracks.title
338   existing_single_track.cd.artist.cds.genreid
339   tracks.lyrics.existing_lyric_versions.lyric_id
340 /];
341
342 is_deeply (
343   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
344   {
345     -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
346
347     existing_single_track => {
348       -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
349       -is_single => 1,
350
351       cd => {
352         -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
353         -is_single => 1,
354
355         artist => {
356           -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
357           -is_single => 1,
358
359           cds => {
360             -identifying_columns => [ 1, 6 ], # existing_single_track.cd.artist.cds.cdid
361             -is_optional => 1,
362
363             tracks => {
364               -identifying_columns => [ 1, 6, 8 ], # existing_single_track.cd.artist.cds.cdid, existing_single_track.cd.artist.cds.tracks.title
365               -is_optional => 1,
366             }
367           }
368         }
369       }
370     },
371     tracks => {
372       -identifying_columns => [ 1, 5 ], # existing_single_track.cd.artist.artistid, tracks.title
373       -is_optional => 1,
374
375       lyrics => {
376         -identifying_columns => [ 1, 5, 10 ], # existing_single_track.cd.artist.artistid, tracks.title, tracks.lyrics.existing_lyric_versions.lyric_id
377         -is_single => 1,
378         -is_optional => 1,
379
380         existing_lyric_versions => {
381           -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
382         },
383       },
384     }
385   },
386   'Correct collapse map constructed',
387 );
388
389 is_same_src (
390   $schema->source ('CD')->_mk_row_parser({
391     inflate_map => $infmap,
392     collapse => 1,
393   }),
394   ' my ($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0,0);
395
396     while ($cur_row_data = (
397       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
398         ||
399       ( $_[1] and $_[1]->() )
400     ) {
401
402       $cur_row_ids{$_} = defined $cur_row_data->[$_] ? $cur_row_data->[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
403         for (0, 1, 5, 6, 8, 10);
404
405       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
406       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
407         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{1}} );
408
409       $collapse_idx[0]{$cur_row_ids{1}} ||= [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
410
411       $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}} ||= [];
412       $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}} ||= [];
413       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}} ||= [{ artistid => $cur_row_data->[1] }];
414
415       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
416         and
417       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
418         $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] }]
419       );
420       defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ );
421
422       (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
423         and
424       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
425         $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
426       );
427       defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ );
428
429       (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
430         and
431       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
432         $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
433       );
434       defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ );
435
436       $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}} ||= [];
437       defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ );
438
439       (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
440         and
441       push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
442         $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] }]
443       );
444
445       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{1}}
446         if $is_new_res;
447     }
448
449     splice @{$_[0]}, $result_pos;
450   ',
451   'Multiple has_many on multiple branches torture test',
452 );
453
454 is_same_src (
455   $schema->source ('CD')->_mk_row_parser({
456     inflate_map => $infmap,
457     collapse => 1,
458   }),
459   ' my ($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0,0);
460
461     while ($cur_row_data = (
462       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
463         ||
464       ( $_[1] and $_[1]->() )
465     ) {
466
467       $cur_row_ids{$_} = defined $cur_row_data->[$_] ? $cur_row_data->[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
468         for (0, 1, 5, 6, 8, 10);
469
470       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
471       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
472         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{1}} );
473
474       $collapse_idx[0]{$cur_row_ids{1}} ||= [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
475
476       $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}} ||= [];
477       $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}} ||= [];
478       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}} ||= [{ artistid => $cur_row_data->[1] }];
479
480       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
481         and
482       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
483         $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] }]
484       );
485       defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ );
486
487       (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
488         and
489       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
490         $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
491       );
492       defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ );
493
494       (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
495         and
496       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
497         $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
498       );
499       defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ );
500
501       $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}} ||= [];
502       defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ );
503
504       (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
505         and
506       push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
507         $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] }]
508       );
509
510       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{1}}
511         if $is_new_res;
512     }
513
514     splice @{$_[0]}, $result_pos;
515   ',
516   'Multiple has_many on multiple branches with branch torture test',
517 );
518
519 $infmap = [
520   'single_track.trackid',                   # (0) definitive link to root from 1:1:1:1:M:M chain
521   'year',                                   # (1) non-unique
522   'tracks.cd',                              # (2) \ together both uniqueness for second multirel
523   'tracks.title',                           # (3) / and definitive link back to root
524   'single_track.cd.artist.cds.cdid',        # (4) to give uniquiness to ...tracks.title below
525   'single_track.cd.artist.cds.year',        # (5) non-unique
526   'single_track.cd.artist.artistid',        # (6) uniqufies entire parental chain
527   'single_track.cd.artist.cds.genreid',     # (7) nullable
528   'single_track.cd.artist.cds.tracks.title',# (8) unique when combined with ...cds.cdid above
529 ];
530
531 is_deeply (
532   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
533   {
534     -identifying_columns => [],
535     -identifying_columns_variants => [
536       [ 0 ], [ 2 ],
537     ],
538     single_track => {
539       -identifying_columns => [ 0 ],
540       -is_optional => 1,
541       -is_single => 1,
542       cd => {
543         -identifying_columns => [ 0 ],
544         -is_single => 1,
545         artist => {
546           -identifying_columns => [ 0 ],
547           -is_single => 1,
548           cds => {
549             -identifying_columns => [ 0, 4 ],
550             -is_optional => 1,
551             tracks => {
552               -identifying_columns => [ 0, 4, 8 ],
553               -is_optional => 1,
554             }
555           }
556         }
557       }
558     },
559     tracks => {
560       -identifying_columns => [ 2, 3 ],
561       -is_optional => 1,
562     }
563   },
564   'Correct underdefined root collapse map constructed'
565 );
566
567 is_same_src (
568   $schema->source ('CD')->_mk_row_parser({
569     inflate_map => $infmap,
570     collapse => 1,
571   }),
572   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
573
574     while ($cur_row_data = (
575       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
576         ||
577       ( $_[1] and $_[1]->() )
578     ) {
579
580       $cur_row_ids{$_} = defined $$cur_row_data[$_] ? $$cur_row_data[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
581         for (0, 2, 3, 4, 8);
582
583       # cache expensive set of ops in a non-existent rowid slot
584       $cur_row_ids{10} = (
585         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_data->[0], q{} ))
586           or
587         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_data->[2], q{} ))
588           or
589         "\0$rows_pos\0"
590       );
591
592       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
593       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
594         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{10}} );
595
596       $collapse_idx[0]{$cur_row_ids{10}} ||= [{ year => $$cur_row_data[1] }];
597
598       $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track} ||= ($collapse_idx[1]{$cur_row_ids{0}} ||= [{ trackid => $cur_row_data->[0] }]);
599       defined($cur_row_data->[0]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track}, __NBC__ );
600
601       $collapse_idx[1]{$cur_row_ids{0}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{0}} ||= [];
602
603       $collapse_idx[2]{$cur_row_ids{0}}[1]{artist} ||= ($collapse_idx[3]{$cur_row_ids{0}} ||= [{ artistid => $cur_row_data->[6] }]);
604
605       (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
606         and
607       push @{$collapse_idx[3]{$cur_row_ids{0}}[1]{cds}}, (
608           $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] }]
609       );
610       defined($cur_row_data->[4]) or bless ( $collapse_idx[3]{$cur_row_ids{0}}[1]{cds}, __NBC__ );
611
612       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
613         and
614       push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}}, (
615           $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
616       );
617       defined($cur_row_data->[8]) or bless ( $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}, __NBC__ );
618
619       (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
620         and
621       push @{$collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}}, (
622           $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = [{ cd => $$cur_row_data[2], title => $cur_row_data->[3] }]
623       );
624       defined($cur_row_data->[2]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}, __NBC__ );
625
626       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{10}}
627         if $is_new_res;
628     }
629
630     splice @{$_[0]}, $result_pos;
631   ',
632   'Multiple has_many on multiple branches with underdefined root torture test',
633 );
634
635 is_same_src (
636   $schema->source ('CD')->_mk_row_parser({
637     inflate_map => $infmap,
638     collapse => 1,
639     hri_style => 1,
640   }),
641   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
642
643     while ($cur_row_data = (
644       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
645         ||
646       ( $_[1] and $_[1]->() )
647     ) {
648
649       $cur_row_ids{$_} = defined $$cur_row_data[$_] ? $$cur_row_data[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
650         for (0, 2, 3, 4, 8);
651
652       # cache expensive set of ops in a non-existent rowid slot
653       $cur_row_ids{10} = (
654         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_data->[0], q{} ))
655           or
656         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_data->[2], q{} ))
657           or
658         "\0$rows_pos\0"
659       );
660
661       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
662       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
663         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{10}} );
664
665       $collapse_idx[0]{$cur_row_ids{10}} ||= { year => $$cur_row_data[1] };
666
667       (defined $cur_row_data->[0])
668         and
669       $collapse_idx[0]{$cur_row_ids{10}}{single_track} ||= ($collapse_idx[1]{$cur_row_ids{0}} ||= { trackid => $$cur_row_data[0] });
670       $collapse_idx[0]{$cur_row_ids{10}}{single_track} ||= undef;
671
672       $collapse_idx[1]{$cur_row_ids{0}}{cd} ||= $collapse_idx[2]{$cur_row_ids{0}};
673
674       $collapse_idx[2]{$cur_row_ids{0}}{artist} ||= ($collapse_idx[3]{$cur_row_ids{0}} ||= { artistid => $$cur_row_data[6] });
675
676       (defined $cur_row_data->[4])
677         and
678       (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
679         and
680       push @{$collapse_idx[3]{$cur_row_ids{0}}{cds}}, (
681           $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] }
682       );
683       $collapse_idx[3]{$cur_row_ids{0}}{cds} ||= [];
684
685       (defined $cur_row_data->[8])
686         and
687       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
688         and
689       push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}{tracks}}, (
690           $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = { title => $$cur_row_data[8] }
691       );
692       $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}{tracks} ||= [];
693
694       (defined $cur_row_data->[2])
695         and
696       (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
697         and
698       push @{$collapse_idx[0]{$cur_row_ids{10}}{tracks}}, (
699           $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = { cd => $$cur_row_data[2], title => $$cur_row_data[3] }
700       );
701       $collapse_idx[0]{$cur_row_ids{10}}{tracks} ||= [];
702
703       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{10}}
704         if $is_new_res;
705     }
706
707     splice @{$_[0]}, $result_pos;
708   ',
709   'Multiple has_many on multiple branches with underdefined root, HRI-direct torture test',
710 );
711
712 done_testing;
713
714 my $deparser;
715 sub is_same_src {
716   $deparser ||= B::Deparse->new;
717   local $Test::Builder::Level = $Test::Builder::Level + 1;
718
719   my ($got, $expect) = @_;
720
721   $expect =~ s/__NBC__/B::perlstring($DBIx::Class::ResultSource::RowParser::Util::null_branch_class)/ge;
722
723   $expect = "  { use strict; use warnings FATAL => 'all';\n$expect\n  }";
724
725   my @normalized = map {
726     my $cref = eval "sub { $_ }" or do {
727       fail "Coderef does not compile!\n\n$@\n\n$_";
728       return undef;
729     };
730     $deparser->coderef2text($cref);
731   } ($got, $expect);
732
733   &is (@normalized, $_[2]||() ) or do {
734     eval { require Test::Differences }
735       ? &Test::Differences::eq_or_diff( @normalized, $_[2]||() )
736       : note ("Original sources:\n\n$got\n\n$expect\n");
737     BAIL_OUT('');
738   };
739 }