Massively refactor and sanify condition collapsing
[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   }))[0],
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   }))[0],
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     prune_null_branches => 1,
142     inflate_map => $infmap,
143   }))[0],
144   '$_ = [
145     { artist => $_->[5], title => $_->[4], year => $_->[2] },
146     {
147       single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) ) ? undef : [
148         undef,
149         {
150           cd => [
151             undef,
152             {
153               artist => [
154                 { artistid => $_->[1] },
155                 {
156                   cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) ) ? undef : [
157                     { cdid => $_->[3] },
158                     {
159                       tracks => ( ! defined $_->[0] ) ? undef : [
160                         { title => $_->[0] },
161                       ]
162                     }
163                   ]
164                 }
165               ]
166             }
167           ]
168         }
169       ]
170     }
171   ] for @{$_[0]}',
172   '1:1 descending non-collapsing pruning parser terminating with chained 1:M:M',
173 );
174
175 is_same_src (
176   ($schema->source ('CD')->_mk_row_parser({
177     hri_style => 1,
178     prune_null_branches => 1,
179     inflate_map => $infmap,
180   }))[0],
181   '$_ = {
182       artist => $_->[5], title => $_->[4], year => $_->[2],
183
184       ( single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
185         ? undef
186         : {
187             cd =>
188               {
189                 artist => {
190                     artistid => $_->[1],
191                     ( cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
192                       ? undef
193                       : {
194                           cdid => $_->[3],
195                           ( tracks => ( ! defined $_->[0] )
196                             ? undef
197                             : { title => $_->[0] }
198                           )
199                         }
200                     )
201                   }
202               }
203           }
204       )
205     } for @{$_[0]}',
206   '1:1 descending non-collapsing HRI-direct parser terminating with chained 1:M:M',
207 );
208
209
210
211 is_deeply (
212   ($schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} })),
213   {
214     -identifying_columns => [ 4, 5 ],
215
216     single_track => {
217       -identifying_columns => [ 1, 4, 5 ],
218       -is_optional => 1,
219       -is_single => 1,
220
221       cd => {
222         -identifying_columns => [ 1, 4, 5 ],
223         -is_single => 1,
224
225         artist => {
226           -identifying_columns => [ 1, 4, 5 ],
227           -is_single => 1,
228
229           cds => {
230             -identifying_columns => [ 1, 3, 4, 5 ],
231             -is_optional => 1,
232
233             tracks => {
234               -identifying_columns => [ 0, 1, 3, 4, 5 ],
235               -is_optional => 1,
236             },
237           },
238         },
239       },
240     },
241   },
242   'Correct collapse map for 1:1 descending chain terminating with chained 1:M:M'
243 );
244
245 is_same_src (
246   ($schema->source ('CD')->_mk_row_parser({
247     inflate_map => $infmap,
248     collapse => 1,
249   }))[0],
250   ' my $rows_pos = 0;
251     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
252
253     while ($cur_row_data = (
254       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
255         ||
256       ( $_[1] and $rows_pos = -1 and $_[1]->() )
257     ) ) {
258
259       $cur_row_ids{0} = $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0";
260       $cur_row_ids{1} = $cur_row_data->[1] // "\0NULL\xFF$rows_pos\xFF1\0";
261       $cur_row_ids{3} = $cur_row_data->[3] // "\0NULL\xFF$rows_pos\xFF3\0";
262       $cur_row_ids{4} = $cur_row_data->[4] // "\0NULL\xFF$rows_pos\xFF4\0";
263       $cur_row_ids{5} = $cur_row_data->[5] // "\0NULL\xFF$rows_pos\xFF5\0";
264
265       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
266       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} and (unshift @{$_[2]}, $cur_row_data) and last;
267
268       # the rowdata itself for root node
269       $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} //= $_[0][$result_pos++] = [{ artist => $cur_row_data->[5], title => $cur_row_data->[4], year => $cur_row_data->[2] }];
270
271       # prefetch data of single_track (placed in root)
272       $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}} = [];
273       defined($cur_row_data->[1]) or bless( $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{single_track}, __NBC__ );
274
275       # prefetch data of cd (placed in single_track)
276       $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}} = [];
277
278       # prefetch data of artist ( placed in single_track->cd)
279       $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] }];
280
281       # prefetch data of cds (if available)
282       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
283         and
284       push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cds}}, (
285         $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = [{ cdid => $cur_row_data->[3] }]
286       );
287       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__ );
288
289       # prefetch data of tracks (if available)
290       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
291         and
292       push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{tracks}}, (
293         $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] }]
294       );
295       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__ );
296
297     }
298     $#{$_[0]} = $result_pos - 1;
299   ',
300   'Same 1:1 descending terminating with chained 1:M:M but with collapse',
301 );
302
303 is_same_src (
304   ($schema->source ('CD')->_mk_row_parser({
305     inflate_map => $infmap,
306     collapse => 1,
307     hri_style => 1,
308     prune_null_branches => 1,
309   }))[0],
310   ' my $rows_pos = 0;
311     my ($result_pos, @collapse_idx, $cur_row_data);
312
313     while ($cur_row_data = (
314       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
315         ||
316       ( $_[1] and $rows_pos = -1 and $_[1]->() )
317     ) ) {
318
319       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
320       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_data->[4]}{$cur_row_data->[5]} and (unshift @{$_[2]}, $cur_row_data) and last;
321
322       # the rowdata itself for root node
323       $collapse_idx[0]{$cur_row_data->[4]}{$cur_row_data->[5]} //= $_[0][$result_pos++] = { artist => $cur_row_data->[5], title => $cur_row_data->[4], year => $cur_row_data->[2] };
324
325       # prefetch data of single_track (placed in root)
326       (! defined($cur_row_data->[1]) ) ? $collapse_idx[0]{$cur_row_data->[4]}{$cur_row_data->[5]}{single_track} = undef : do {
327         $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]};
328
329         # prefetch data of cd (placed in single_track)
330         $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]};
331
332         # prefetch data of artist ( placed in single_track->cd)
333         $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] };
334
335         # prefetch data of cds (if available)
336         (! defined $cur_row_data->[3] ) ? $collapse_idx[3]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]}{cds} = [] : do {
337
338           (! $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]} )
339             and
340           push @{$collapse_idx[3]{$cur_row_data->[1]}{$cur_row_data->[4]}{$cur_row_data->[5]}{cds}}, (
341             $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]} = { cdid => $cur_row_data->[3] }
342           );
343
344           # prefetch data of tracks (if available)
345           ( ! 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 {
346
347             (! $collapse_idx[5]{$cur_row_data->[0]}{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]} )
348               and
349             push @{$collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[3]}{$cur_row_data->[4]}{$cur_row_data->[5]}{tracks}}, (
350               $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] }
351             );
352           };
353         };
354       };
355     }
356     $#{$_[0]} = $result_pos - 1;
357   ',
358   'Same 1:1 descending terminating with chained 1:M:M but with collapse, HRI-direct',
359 );
360
361 $infmap = [qw/
362   tracks.lyrics.existing_lyric_versions.text
363   existing_single_track.cd.artist.artistid
364   existing_single_track.cd.artist.cds.year
365   year
366   genreid
367   tracks.title
368   existing_single_track.cd.artist.cds.cdid
369   latest_cd
370   existing_single_track.cd.artist.cds.tracks.title
371   existing_single_track.cd.artist.cds.genreid
372   tracks.lyrics.existing_lyric_versions.lyric_id
373 /];
374
375 is_deeply (
376   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
377   {
378     -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
379
380     existing_single_track => {
381       -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
382       -is_single => 1,
383
384       cd => {
385         -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
386         -is_single => 1,
387
388         artist => {
389           -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
390           -is_single => 1,
391
392           cds => {
393             -identifying_columns => [ 1, 6 ], # existing_single_track.cd.artist.cds.cdid
394             -is_optional => 1,
395
396             tracks => {
397               -identifying_columns => [ 1, 6, 8 ], # existing_single_track.cd.artist.cds.cdid, existing_single_track.cd.artist.cds.tracks.title
398               -is_optional => 1,
399             }
400           }
401         }
402       }
403     },
404     tracks => {
405       -identifying_columns => [ 1, 5 ], # existing_single_track.cd.artist.artistid, tracks.title
406       -is_optional => 1,
407
408       lyrics => {
409         -identifying_columns => [ 1, 5, 10 ], # existing_single_track.cd.artist.artistid, tracks.title, tracks.lyrics.existing_lyric_versions.lyric_id
410         -is_single => 1,
411         -is_optional => 1,
412
413         existing_lyric_versions => {
414           -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
415         },
416       },
417     }
418   },
419   'Correct collapse map constructed',
420 );
421
422 is_same_src (
423   ($schema->source ('CD')->_mk_row_parser({
424     inflate_map => $infmap,
425     collapse => 1,
426   }))[0],
427   ' my $rows_pos = 0;
428     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
429
430     while ($cur_row_data = (
431       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
432         ||
433       ( $_[1] and $rows_pos = -1 and $_[1]->() )
434     ) ) {
435
436       $cur_row_ids{0} = $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0";
437       $cur_row_ids{1} = $cur_row_data->[1] // "\0NULL\xFF$rows_pos\xFF1\0";
438       $cur_row_ids{5} = $cur_row_data->[5] // "\0NULL\xFF$rows_pos\xFF5\0";
439       $cur_row_ids{6} = $cur_row_data->[6] // "\0NULL\xFF$rows_pos\xFF6\0";
440       $cur_row_ids{8} = $cur_row_data->[8] // "\0NULL\xFF$rows_pos\xFF8\0";
441       $cur_row_ids{10} = $cur_row_data->[10] // "\0NULL\xFF$rows_pos\xFF10\0";
442
443       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
444       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{1}} and (unshift @{$_[2]}, $cur_row_data) and last;
445
446       $collapse_idx[0]{$cur_row_ids{1}} //= $_[0][$result_pos++] = [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
447
448       $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} //= $collapse_idx[1]{$cur_row_ids{1}} = [];
449       $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} //= $collapse_idx[2]{$cur_row_ids{1}} = [];
450       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} //= $collapse_idx[3]{$cur_row_ids{1}} = [{ artistid => $cur_row_data->[1] }];
451
452       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
453         and
454       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
455         $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] }]
456       );
457       defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ );
458
459       (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
460         and
461       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
462         $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
463       );
464       defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ );
465
466       (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
467         and
468       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
469         $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
470       );
471       defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ );
472
473       $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}} = [];
474       defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ );
475
476       (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
477         and
478       push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
479         $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] }]
480       );
481     }
482
483     $#{$_[0]} = $result_pos - 1;
484   ',
485   'Multiple has_many on multiple branches torture test',
486 );
487
488 is_same_src (
489   ($schema->source ('CD')->_mk_row_parser({
490     inflate_map => $infmap,
491     collapse => 1,
492     prune_null_branches => 1,
493   }))[0],
494   ' my $rows_pos = 0;
495     my ($result_pos, @collapse_idx, $cur_row_data);
496
497     while ($cur_row_data = (
498       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
499         ||
500       ( $_[1] and $rows_pos = -1 and $_[1]->() )
501     ) ) {
502
503       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
504       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_data->[1]} and (unshift @{$_[2]}, $cur_row_data) and last;
505
506       $collapse_idx[0]{$cur_row_data->[1]} //= $_[0][$result_pos++] = [{ genreid => $cur_row_data->[4], latest_cd => $cur_row_data->[7], year => $cur_row_data->[3] }];
507
508       $collapse_idx[0]{$cur_row_data->[1]}[1]{existing_single_track} //= $collapse_idx[1]{$cur_row_data->[1]} = [];
509       $collapse_idx[1]{$cur_row_data->[1]}[1]{cd} //= $collapse_idx[2]{$cur_row_data->[1]} = [];
510       $collapse_idx[2]{$cur_row_data->[1]}[1]{artist} //= $collapse_idx[3]{$cur_row_data->[1]} = [{ artistid => $cur_row_data->[1] }];
511
512       (! defined($cur_row_data->[6])) ? $collapse_idx[3]{$cur_row_data->[1]}[1]{cds} = [] : do {
513         (! $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[6]} )
514           and
515         push @{ $collapse_idx[3]{$cur_row_data->[1]}[1]{cds} }, (
516           $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[6]} = [{ cdid => $cur_row_data->[6], genreid => $cur_row_data->[9], year => $cur_row_data->[2] }]
517         );
518
519         (! defined($cur_row_data->[8]) ) ? $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[6]}[1]{tracks} = [] : do {
520
521           (! $collapse_idx[5]{$cur_row_data->[1]}{$cur_row_data->[6]}{$cur_row_data->[8]} )
522             and
523           push @{ $collapse_idx[4]{$cur_row_data->[1]}{$cur_row_data->[6]}[1]{tracks} }, (
524             $collapse_idx[5]{$cur_row_data->[1]}{$cur_row_data->[6]}{$cur_row_data->[8]} = [{ title => $cur_row_data->[8] }]
525           );
526         };
527       };
528
529       (! defined($cur_row_data->[5]) ) ? $collapse_idx[0]{$cur_row_data->[1]}[1]{tracks} = [] : do {
530
531         (! $collapse_idx[6]{$cur_row_data->[1]}{$cur_row_data->[5]} )
532           and
533         push @{ $collapse_idx[0]{$cur_row_data->[1]}[1]{tracks} }, (
534           $collapse_idx[6]{$cur_row_data->[1]}{$cur_row_data->[5]} = [{ title => $cur_row_data->[5] }]
535         );
536
537         (! defined($cur_row_data->[10]) ) ? $collapse_idx[6]{$cur_row_data->[1]}{$cur_row_data->[5]}[1]{lyrics} = [] : do {
538
539           $collapse_idx[6]{$cur_row_data->[1]}{$cur_row_data->[5]}[1]{lyrics} //= $collapse_idx[7]{$cur_row_data->[1]}{$cur_row_data->[5]}{$cur_row_data->[10]} = [];
540
541           (! $collapse_idx[8]{$cur_row_data->[0]}{$cur_row_data->[1]}{$cur_row_data->[5]}{$cur_row_data->[10]} )
542             and
543           push @{ $collapse_idx[7]{$cur_row_data->[1]}{$cur_row_data->[5]}{$cur_row_data->[10]}[1]{existing_lyric_versions} }, (
544             $collapse_idx[8]{$cur_row_data->[0]}{$cur_row_data->[1]}{$cur_row_data->[5]}{$cur_row_data->[10]} = [{ lyric_id => $cur_row_data->[10], text => $cur_row_data->[0] }]
545           );
546         };
547       };
548     }
549
550     $#{$_[0]} = $result_pos - 1;
551   ',
552   'Multiple has_many on multiple branches with branch pruning torture test',
553 );
554
555 $infmap = [
556   'single_track.trackid',                   # (0) definitive link to root from 1:1:1:1:M:M chain
557   'year',                                   # (1) non-unique
558   'tracks.cd',                              # (2) \ together both uniqueness for second multirel
559   'tracks.title',                           # (3) / and definitive link back to root
560   'single_track.cd.artist.cds.cdid',        # (4) to give uniquiness to ...tracks.title below
561   'single_track.cd.artist.cds.year',        # (5) non-unique
562   'single_track.cd.artist.artistid',        # (6) uniqufies entire parental chain
563   'single_track.cd.artist.cds.genreid',     # (7) nullable
564   'single_track.cd.artist.cds.tracks.title',# (8) unique when combined with ...cds.cdid above
565 ];
566
567 is_deeply (
568   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
569   {
570     -identifying_columns => [],
571     -identifying_columns_variants => [
572       [ 0 ], [ 2 ],
573     ],
574     single_track => {
575       -identifying_columns => [ 0 ],
576       -is_optional => 1,
577       -is_single => 1,
578       cd => {
579         -identifying_columns => [ 0 ],
580         -is_single => 1,
581         artist => {
582           -identifying_columns => [ 0 ],
583           -is_single => 1,
584           cds => {
585             -identifying_columns => [ 0, 4 ],
586             -is_optional => 1,
587             tracks => {
588               -identifying_columns => [ 0, 4, 8 ],
589               -is_optional => 1,
590             }
591           }
592         }
593       }
594     },
595     tracks => {
596       -identifying_columns => [ 2, 3 ],
597       -is_optional => 1,
598     }
599   },
600   'Correct underdefined root collapse map constructed'
601 );
602
603 is_same_src (
604   ($schema->source ('CD')->_mk_row_parser({
605     inflate_map => $infmap,
606     collapse => 1,
607   }))[0],
608   ' my $rows_pos = 0;
609     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
610
611     while ($cur_row_data = (
612       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
613         ||
614       ( $_[1] and $rows_pos = -1 and $_[1]->() )
615     ) ) {
616
617       $cur_row_ids{0} = $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0";
618       $cur_row_ids{2} = $cur_row_data->[2] // "\0NULL\xFF$rows_pos\xFF2\0";
619       $cur_row_ids{3} = $cur_row_data->[3] // "\0NULL\xFF$rows_pos\xFF3\0";
620       $cur_row_ids{4} = $cur_row_data->[4] // "\0NULL\xFF$rows_pos\xFF4\0";
621       $cur_row_ids{8} = $cur_row_data->[8] // "\0NULL\xFF$rows_pos\xFF8\0";
622
623       # cache expensive set of ops in a non-existent rowid slot
624       $cur_row_ids{10} = (
625         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_data->[0], q{} ))
626           or
627         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_data->[2], q{} ))
628           or
629         "\0$rows_pos\0"
630       );
631
632       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
633       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{10}} and (unshift @{$_[2]}, $cur_row_data) and last;
634
635       $collapse_idx[0]{$cur_row_ids{10}} //= $_[0][$result_pos++] = [{ year => $$cur_row_data[1] }];
636
637       $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track} //= ($collapse_idx[1]{$cur_row_ids{0}} = [{ trackid => $cur_row_data->[0] }]);
638       defined($cur_row_data->[0]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track}, __NBC__ );
639
640       $collapse_idx[1]{$cur_row_ids{0}}[1]{cd} //= $collapse_idx[2]{$cur_row_ids{0}} = [];
641
642       $collapse_idx[2]{$cur_row_ids{0}}[1]{artist} //= ($collapse_idx[3]{$cur_row_ids{0}} = [{ artistid => $cur_row_data->[6] }]);
643
644       (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
645         and
646       push @{$collapse_idx[3]{$cur_row_ids{0}}[1]{cds}}, (
647           $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] }]
648       );
649       defined($cur_row_data->[4]) or bless ( $collapse_idx[3]{$cur_row_ids{0}}[1]{cds}, __NBC__ );
650
651       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
652         and
653       push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}}, (
654           $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
655       );
656       defined($cur_row_data->[8]) or bless ( $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}, __NBC__ );
657
658       (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
659         and
660       push @{$collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}}, (
661           $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = [{ cd => $$cur_row_data[2], title => $cur_row_data->[3] }]
662       );
663       defined($cur_row_data->[2]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}, __NBC__ );
664     }
665
666     $#{$_[0]} = $result_pos - 1;
667   ',
668   'Multiple has_many on multiple branches with underdefined root torture test',
669 );
670
671 is_same_src (
672   ($schema->source ('CD')->_mk_row_parser({
673     inflate_map => $infmap,
674     collapse => 1,
675     hri_style => 1,
676     prune_null_branches => 1,
677   }))[0],
678   ' my $rows_pos = 0;
679     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
680
681     while ($cur_row_data = (
682       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
683         ||
684       ( $_[1] and $rows_pos = -1 and $_[1]->() )
685     ) ) {
686
687       # do not care about nullability here
688       $cur_row_ids{0} = $cur_row_data->[0];
689       $cur_row_ids{2} = $cur_row_data->[2];
690       $cur_row_ids{3} = $cur_row_data->[3];
691       $cur_row_ids{4} = $cur_row_data->[4];
692       $cur_row_ids{8} = $cur_row_data->[8];
693
694       # cache expensive set of ops in a non-existent rowid slot
695       $cur_row_ids{10} = (
696         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_data->[0], q{} ))
697           or
698         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_data->[2], q{} ))
699           or
700         "\0$rows_pos\0"
701       );
702
703       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
704       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{10}} and (unshift @{$_[2]}, $cur_row_data) and last;
705
706       $collapse_idx[0]{$cur_row_ids{10}} //= $_[0][$result_pos++] = { year => $$cur_row_data[1] };
707
708       (! defined $cur_row_data->[0] ) ? $collapse_idx[0]{$cur_row_ids{10}}{single_track} = undef : do {
709
710         $collapse_idx[0]{$cur_row_ids{10}}{single_track} //= ($collapse_idx[1]{$cur_row_ids{0}} = { trackid => $$cur_row_data[0] });
711
712         $collapse_idx[1]{$cur_row_ids{0}}{cd} //= $collapse_idx[2]{$cur_row_ids{0}};
713
714         $collapse_idx[2]{$cur_row_ids{0}}{artist} //= ($collapse_idx[3]{$cur_row_ids{0}} = { artistid => $$cur_row_data[6] });
715
716         (! defined $cur_row_data->[4] ) ? $collapse_idx[3]{$cur_row_ids{0}}{cds} = [] : do {
717
718           (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
719             and
720           push @{$collapse_idx[3]{$cur_row_ids{0}}{cds}}, (
721               $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] }
722           );
723
724           (! defined $cur_row_data->[8] ) ? $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}{tracks} = [] : do {
725
726             (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
727               and
728             push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}{tracks}}, (
729                 $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = { title => $$cur_row_data[8] }
730             );
731           };
732         };
733       };
734
735       (! defined $cur_row_data->[2] ) ? $collapse_idx[0]{$cur_row_ids{10}}{tracks} = [] : do {
736         (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
737           and
738         push @{$collapse_idx[0]{$cur_row_ids{10}}{tracks}}, (
739             $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = { cd => $$cur_row_data[2], title => $$cur_row_data[3] }
740         );
741       };
742     }
743
744     $#{$_[0]} = $result_pos - 1;
745   ',
746   'Multiple has_many on multiple branches with underdefined root, HRI-direct torture test',
747 );
748
749 done_testing;
750
751 my $deparser;
752 sub is_same_src { SKIP: {
753   $deparser ||= B::Deparse->new;
754   local $Test::Builder::Level = $Test::Builder::Level + 1;
755
756   my ($got, $expect) = @_;
757
758   skip "Not testing equality of source containing defined-or operator on this perl $]", 1
759     if ($] < 5.010 and$expect =~ m!\Q//=!);
760
761   $expect =~ s/__NBC__/B::perlstring($DBIx::Class::ResultSource::RowParser::Util::null_branch_class)/ge;
762
763   $expect = "  { use strict; use warnings FATAL => 'all';\n$expect\n  }";
764
765   my @normalized = map {
766     my $cref = eval "sub { $_ }" or do {
767       fail "Coderef does not compile!\n\n$@\n\n$_";
768       return undef;
769     };
770     $deparser->coderef2text($cref);
771   } ($got, $expect);
772
773   &is (@normalized, $_[2]||() ) or do {
774     eval { require Test::Differences }
775       ? &Test::Differences::eq_or_diff( @normalized, $_[2]||() )
776       : note ("Original sources:\n\n$got\n\n$expect\n")
777     ;
778     exit 1;
779   };
780 } }