BAIL_OUT stops the entire test run - do not want that
[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, @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       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
280       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
281         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_data->[4]}{$cur_row_data->[5]} );
282
283       # the rowdata itself for root node
284       $collapse_idx[0]{$cur_row_data->[4]}{$cur_row_data->[5]} ||= { artist => $cur_row_data->[5], title => $cur_row_data->[4], year => $cur_row_data->[2] };
285
286       # prefetch data of single_track (placed in root)
287       (! defined($cur_row_data->[1]) ) ? $collapse_idx[0]{$cur_row_data->[4]}{$cur_row_data->[5]}{single_track} = undef : do {
288         $collapse_idx[0]{$cur_row_data->[4]}{$cur_row_data->[5]}{single_track} ||= $collapse_idx[1]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]};
289
290         # prefetch data of cd (placed in single_track)
291         $collapse_idx[1]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]}{cd} ||= $collapse_idx[2]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]};
292
293         # prefetch data of artist ( placed in single_track->cd)
294         $collapse_idx[2]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]}{artist} ||= $collapse_idx[3]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]} ||= { artistid => $cur_row_data->[1] };
295
296         # prefetch data of cds (if available)
297         (! defined $cur_row_data->[3] ) ? $collapse_idx[3]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]}{cds} = [] : do {
298
299           (! $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]} )
300             and
301           push @{$collapse_idx[3]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]}{cds}}, (
302             $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]} = { cdid => $cur_row_data->[3] }
303           );
304
305           # prefetch data of tracks (if available)
306           ( ! defined $cur_row_data->[0] ) ? $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]}{tracks} = [] : do {
307
308             (! $collapse_idx[5]{$cur_row_data->[0]}{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]} )
309               and
310             push @{$collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]}{tracks}}, (
311               $collapse_idx[5]{$cur_row_data->[0]}{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]} = { title => $cur_row_data->[0] }
312             );
313           };
314         };
315       };
316
317       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_data->[4]}{$cur_row_data->[5]}
318         if $is_new_res;
319     }
320     splice @{$_[0]}, $result_pos;
321   ',
322   'Same 1:1 descending terminating with chained 1:M:M but with collapse, HRI-direct',
323 );
324
325 $infmap = [qw/
326   tracks.lyrics.existing_lyric_versions.text
327   existing_single_track.cd.artist.artistid
328   existing_single_track.cd.artist.cds.year
329   year
330   genreid
331   tracks.title
332   existing_single_track.cd.artist.cds.cdid
333   latest_cd
334   existing_single_track.cd.artist.cds.tracks.title
335   existing_single_track.cd.artist.cds.genreid
336   tracks.lyrics.existing_lyric_versions.lyric_id
337 /];
338
339 is_deeply (
340   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
341   {
342     -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
343
344     existing_single_track => {
345       -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
346       -is_single => 1,
347
348       cd => {
349         -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
350         -is_single => 1,
351
352         artist => {
353           -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
354           -is_single => 1,
355
356           cds => {
357             -identifying_columns => [ 1, 6 ], # existing_single_track.cd.artist.cds.cdid
358             -is_optional => 1,
359
360             tracks => {
361               -identifying_columns => [ 1, 6, 8 ], # existing_single_track.cd.artist.cds.cdid, existing_single_track.cd.artist.cds.tracks.title
362               -is_optional => 1,
363             }
364           }
365         }
366       }
367     },
368     tracks => {
369       -identifying_columns => [ 1, 5 ], # existing_single_track.cd.artist.artistid, tracks.title
370       -is_optional => 1,
371
372       lyrics => {
373         -identifying_columns => [ 1, 5, 10 ], # existing_single_track.cd.artist.artistid, tracks.title, tracks.lyrics.existing_lyric_versions.lyric_id
374         -is_single => 1,
375         -is_optional => 1,
376
377         existing_lyric_versions => {
378           -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
379         },
380       },
381     }
382   },
383   'Correct collapse map constructed',
384 );
385
386 is_same_src (
387   $schema->source ('CD')->_mk_row_parser({
388     inflate_map => $infmap,
389     collapse => 1,
390   }),
391   ' my ($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0,0);
392
393     while ($cur_row_data = (
394       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
395         ||
396       ( $_[1] and $_[1]->() )
397     ) {
398
399       $cur_row_ids{$_} = defined $cur_row_data->[$_] ? $cur_row_data->[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
400         for (0, 1, 5, 6, 8, 10);
401
402       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
403       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
404         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{1}} );
405
406       $collapse_idx[0]{$cur_row_ids{1}} ||= [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
407
408       $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}} ||= [];
409       $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}} ||= [];
410       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}} ||= [{ artistid => $cur_row_data->[1] }];
411
412       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
413         and
414       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
415         $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] }]
416       );
417       defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ );
418
419       (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
420         and
421       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
422         $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
423       );
424       defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ );
425
426       (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
427         and
428       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
429         $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
430       );
431       defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ );
432
433       $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}} ||= [];
434       defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ );
435
436       (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
437         and
438       push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
439         $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] }]
440       );
441
442       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{1}}
443         if $is_new_res;
444     }
445
446     splice @{$_[0]}, $result_pos;
447   ',
448   'Multiple has_many on multiple branches torture test',
449 );
450
451 is_same_src (
452   $schema->source ('CD')->_mk_row_parser({
453     inflate_map => $infmap,
454     collapse => 1,
455   }),
456   ' my ($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0,0);
457
458     while ($cur_row_data = (
459       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
460         ||
461       ( $_[1] and $_[1]->() )
462     ) {
463
464       $cur_row_ids{$_} = defined $cur_row_data->[$_] ? $cur_row_data->[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
465         for (0, 1, 5, 6, 8, 10);
466
467       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
468       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
469         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{1}} );
470
471       $collapse_idx[0]{$cur_row_ids{1}} ||= [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
472
473       $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} ||= $collapse_idx[1]{$cur_row_ids{1}} ||= [];
474       $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{1}} ||= [];
475       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} ||= $collapse_idx[3]{$cur_row_ids{1}} ||= [{ artistid => $cur_row_data->[1] }];
476
477       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
478         and
479       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
480         $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] }]
481       );
482       defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ );
483
484       (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
485         and
486       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
487         $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
488       );
489       defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ );
490
491       (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
492         and
493       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
494         $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
495       );
496       defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ );
497
498       $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}} ||= [];
499       defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ );
500
501       (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
502         and
503       push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
504         $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] }]
505       );
506
507       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{1}}
508         if $is_new_res;
509     }
510
511     splice @{$_[0]}, $result_pos;
512   ',
513   'Multiple has_many on multiple branches with branch torture test',
514 );
515
516 $infmap = [
517   'single_track.trackid',                   # (0) definitive link to root from 1:1:1:1:M:M chain
518   'year',                                   # (1) non-unique
519   'tracks.cd',                              # (2) \ together both uniqueness for second multirel
520   'tracks.title',                           # (3) / and definitive link back to root
521   'single_track.cd.artist.cds.cdid',        # (4) to give uniquiness to ...tracks.title below
522   'single_track.cd.artist.cds.year',        # (5) non-unique
523   'single_track.cd.artist.artistid',        # (6) uniqufies entire parental chain
524   'single_track.cd.artist.cds.genreid',     # (7) nullable
525   'single_track.cd.artist.cds.tracks.title',# (8) unique when combined with ...cds.cdid above
526 ];
527
528 is_deeply (
529   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
530   {
531     -identifying_columns => [],
532     -identifying_columns_variants => [
533       [ 0 ], [ 2 ],
534     ],
535     single_track => {
536       -identifying_columns => [ 0 ],
537       -is_optional => 1,
538       -is_single => 1,
539       cd => {
540         -identifying_columns => [ 0 ],
541         -is_single => 1,
542         artist => {
543           -identifying_columns => [ 0 ],
544           -is_single => 1,
545           cds => {
546             -identifying_columns => [ 0, 4 ],
547             -is_optional => 1,
548             tracks => {
549               -identifying_columns => [ 0, 4, 8 ],
550               -is_optional => 1,
551             }
552           }
553         }
554       }
555     },
556     tracks => {
557       -identifying_columns => [ 2, 3 ],
558       -is_optional => 1,
559     }
560   },
561   'Correct underdefined root collapse map constructed'
562 );
563
564 is_same_src (
565   $schema->source ('CD')->_mk_row_parser({
566     inflate_map => $infmap,
567     collapse => 1,
568   }),
569   ' my($rows_pos, $result_pos, $cur_row_data, %cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
570
571     while ($cur_row_data = (
572       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
573         ||
574       ( $_[1] and $_[1]->() )
575     ) {
576
577       $cur_row_ids{$_} = defined $$cur_row_data[$_] ? $$cur_row_data[$_] : "\0NULL\xFF$rows_pos\xFF$_\0"
578         for (0, 2, 3, 4, 8);
579
580       # cache expensive set of ops in a non-existent rowid slot
581       $cur_row_ids{10} = (
582         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_data->[0], q{} ))
583           or
584         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_data->[2], q{} ))
585           or
586         "\0$rows_pos\0"
587       );
588
589       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
590       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
591         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_ids{10}} );
592
593       $collapse_idx[0]{$cur_row_ids{10}} ||= [{ year => $$cur_row_data[1] }];
594
595       $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track} ||= ($collapse_idx[1]{$cur_row_ids{0}} ||= [{ trackid => $cur_row_data->[0] }]);
596       defined($cur_row_data->[0]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track}, __NBC__ );
597
598       $collapse_idx[1]{$cur_row_ids{0}}[1]{cd} ||= $collapse_idx[2]{$cur_row_ids{0}} ||= [];
599
600       $collapse_idx[2]{$cur_row_ids{0}}[1]{artist} ||= ($collapse_idx[3]{$cur_row_ids{0}} ||= [{ artistid => $cur_row_data->[6] }]);
601
602       (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
603         and
604       push @{$collapse_idx[3]{$cur_row_ids{0}}[1]{cds}}, (
605           $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] }]
606       );
607       defined($cur_row_data->[4]) or bless ( $collapse_idx[3]{$cur_row_ids{0}}[1]{cds}, __NBC__ );
608
609       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
610         and
611       push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}}, (
612           $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
613       );
614       defined($cur_row_data->[8]) or bless ( $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}, __NBC__ );
615
616       (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
617         and
618       push @{$collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}}, (
619           $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = [{ cd => $$cur_row_data[2], title => $cur_row_data->[3] }]
620       );
621       defined($cur_row_data->[2]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}, __NBC__ );
622
623       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_ids{10}}
624         if $is_new_res;
625     }
626
627     splice @{$_[0]}, $result_pos;
628   ',
629   'Multiple has_many on multiple branches with underdefined root torture test',
630 );
631
632 is_same_src (
633   $schema->source ('CD')->_mk_row_parser({
634     inflate_map => $infmap,
635     collapse => 1,
636     hri_style => 1,
637   }),
638   ' my($rows_pos, $result_pos, $cur_row_data, @collapse_idx, $is_new_res) = (0, 0);
639
640     while ($cur_row_data = (
641       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
642         ||
643       ( $_[1] and $_[1]->() )
644     ) {
645
646       # cache expensive set of ops in a non-existent rowid slot
647       $cur_row_data->[10] = (
648         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_data->[0], q{} ))
649           or
650         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_data->[2], q{} ))
651           or
652         "\0$rows_pos\0"
653       );
654
655       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
656       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row_data) and last
657         if ( $is_new_res = ! $collapse_idx[0]{$cur_row_data->[10]} );
658
659       $collapse_idx[0]{$cur_row_data->[10]} ||= { year => $$cur_row_data[1] };
660
661       (! defined $cur_row_data->[0] ) ? $collapse_idx[0]{$cur_row_data->[10]}{single_track} = undef : do {
662
663         $collapse_idx[0]{$cur_row_data->[10]}{single_track} ||= ($collapse_idx[1]{$cur_row_data->[0]} ||= { trackid => $$cur_row_data[0] });
664
665         $collapse_idx[1]{$cur_row_data->[0]}{cd} ||= $collapse_idx[2]{$cur_row_data->[0]};
666
667         $collapse_idx[2]{$cur_row_data->[0]}{artist} ||= ($collapse_idx[3]{$cur_row_data->[0]} ||= { artistid => $$cur_row_data[6] });
668
669         (! defined $cur_row_data->[4] ) ? $collapse_idx[3]{$cur_row_data->[0]}{cds} = [] : do {
670
671           (! $collapse_idx[4]{$cur_row_data->[0]}{$cur_row_data->[4]} )
672             and
673           push @{$collapse_idx[3]{$cur_row_data->[0]}{cds}}, (
674               $collapse_idx[4]{$cur_row_data->[0]}{$cur_row_data->[4]} = { cdid => $$cur_row_data[4], genreid => $$cur_row_data[7], year => $$cur_row_data[5] }
675           );
676
677           (! defined $cur_row_data->[8] ) ? $collapse_idx[4]{$cur_row_data->[0]}{$cur_row_data->[4]}{tracks} = [] : do {
678
679             (! $collapse_idx[5]{$cur_row_data->[0]}{$cur_row_data->[4]}{$cur_row_data->[8]} )
680               and
681             push @{$collapse_idx[4]{$cur_row_data->[0]}{$cur_row_data->[4]}{tracks}}, (
682                 $collapse_idx[5]{$cur_row_data->[0]}{$cur_row_data->[4]}{$cur_row_data->[8]} = { title => $$cur_row_data[8] }
683             );
684           };
685         };
686       };
687
688       (! defined $cur_row_data->[2] ) ? $collapse_idx[0]{$cur_row_data->[10]}{tracks} = [] : do {
689         (! $collapse_idx[6]{$cur_row_data->[2]}{$cur_row_data->[3]} )
690           and
691         push @{$collapse_idx[0]{$cur_row_data->[10]}{tracks}}, (
692             $collapse_idx[6]{$cur_row_data->[2]}{$cur_row_data->[3]} = { cd => $$cur_row_data[2], title => $$cur_row_data[3] }
693         );
694       };
695
696       $_[0][$result_pos++] = $collapse_idx[0]{$cur_row_data->[10]}
697         if $is_new_res;
698     }
699
700     splice @{$_[0]}, $result_pos;
701   ',
702   'Multiple has_many on multiple branches with underdefined root, HRI-direct torture test',
703 );
704
705 done_testing;
706
707 my $deparser;
708 sub is_same_src {
709   $deparser ||= B::Deparse->new;
710   local $Test::Builder::Level = $Test::Builder::Level + 1;
711
712   my ($got, $expect) = @_;
713
714   $expect =~ s/__NBC__/B::perlstring($DBIx::Class::ResultSource::RowParser::Util::null_branch_class)/ge;
715
716   $expect = "  { use strict; use warnings FATAL => 'all';\n$expect\n  }";
717
718   my @normalized = map {
719     my $cref = eval "sub { $_ }" or do {
720       fail "Coderef does not compile!\n\n$@\n\n$_";
721       return undef;
722     };
723     $deparser->coderef2text($cref);
724   } ($got, $expect);
725
726   &is (@normalized, $_[2]||() ) or do {
727     eval { require Test::Differences }
728       ? &Test::Differences::eq_or_diff( @normalized, $_[2]||() )
729       : note ("Original sources:\n\n$got\n\n$expect\n")
730     ;
731     exit 1;
732   };
733 }