Introduce GOVERNANCE document and empty RESOLUTIONS file.
[dbsrgits/DBIx-Class.git] / t / resultset / rowparser_internals.t
1 BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) }
2
3 use strict;
4 use warnings;
5
6 use Test::More;
7
8 use DBICTest;
9 use B::Deparse;
10 use DBIx::Class::_Util 'perlstring';
11
12 my $schema = DBICTest->init_schema(no_deploy => 1);
13 my $infmap = [qw/
14   single_track.cd.artist.name
15   year
16 /];
17
18 is_same_src (
19   ($schema->source ('CD')->_mk_row_parser({
20     inflate_map => $infmap,
21   }))[0],
22   '$_ = [
23     { year => $_->[1] },
24     { single_track => ( ! defined( $_->[0]) )
25       ? bless( [
26         undef,
27         { cd => [
28           undef,
29           { artist => [
30             { name  => $_->[0] },
31           ] },
32         ] },
33       ], __NBC__ )
34       : [
35         undef,
36         { cd => [
37           undef,
38           { artist => [
39             { name  => $_->[0] },
40           ] },
41         ] },
42       ]
43     },
44   ] for @{$_[0]}',
45   'Simple 1:1 descending non-collapsing parser',
46 );
47
48 $infmap = [qw/
49   single_track.cd.artist.cds.tracks.title
50   single_track.cd.artist.artistid
51   year
52   single_track.cd.artist.cds.cdid
53   title
54   artist
55 /];
56
57 is_same_src (
58   ($schema->source ('CD')->_mk_row_parser({
59     inflate_map => $infmap,
60   }))[0],
61   '$_ = [
62     { artist => $_->[5], title => $_->[4], year => $_->[2] },
63     {
64       single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
65         ? bless( [
66           undef,
67           {
68             cd => [
69               undef,
70               {
71                 artist => [
72                   { artistid => $_->[1] },
73                   {
74                     cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
75                       ? bless ([
76                         { cdid => $_->[3] },
77                         {
78                           tracks => ( ! defined $_->[0] )
79                             ? bless ( [{ title => $_->[0] }], __NBC__ )
80                             : [{ title => $_->[0] }]
81                         }
82                       ], __NBC__)
83                       : [
84                         { cdid => $_->[3] },
85                         {
86                           tracks => ( ! defined $_->[0] )
87                             ? bless ( [{ title => $_->[0] }], __NBC__ )
88                             : [{ title => $_->[0] }]
89                         }
90                       ]
91                   }
92                 ]
93               }
94             ]
95           }
96         ], __NBC__)
97         : [
98           undef,
99           {
100             cd => [
101               undef,
102               {
103                 artist => [
104                   { artistid => $_->[1] },
105                   {
106                     cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
107                       ? bless ([
108                         { cdid => $_->[3] },
109                         {
110                           tracks => ( ! defined $_->[0] )
111                             ? bless ( [{ title => $_->[0] }], __NBC__ )
112                             : [{ title => $_->[0] }]
113                         }
114                       ], __NBC__)
115                       : [
116                         { cdid => $_->[3] },
117                         {
118                           tracks => ( ! defined $_->[0] )
119                             ? bless ( [{ title => $_->[0] }], __NBC__ )
120                             : [{ title => $_->[0] }]
121                         }
122                       ]
123                   }
124                 ]
125               }
126             ]
127           }
128         ]
129     }
130   ] for @{$_[0]}',
131   '1:1 descending non-collapsing parser terminating with chained 1:M:M',
132 );
133
134 is_same_src (
135   ($schema->source ('CD')->_mk_row_parser({
136     prune_null_branches => 1,
137     inflate_map => $infmap,
138   }))[0],
139   '$_ = [
140     { artist => $_->[5], title => $_->[4], year => $_->[2] },
141     {
142       single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) ) ? undef : [
143         undef,
144         {
145           cd => [
146             undef,
147             {
148               artist => [
149                 { artistid => $_->[1] },
150                 {
151                   cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) ) ? undef : [
152                     { cdid => $_->[3] },
153                     {
154                       tracks => ( ! defined $_->[0] ) ? undef : [
155                         { title => $_->[0] },
156                       ]
157                     }
158                   ]
159                 }
160               ]
161             }
162           ]
163         }
164       ]
165     }
166   ] for @{$_[0]}',
167   '1:1 descending non-collapsing pruning parser terminating with chained 1:M:M',
168 );
169
170 is_same_src (
171   ($schema->source ('CD')->_mk_row_parser({
172     hri_style => 1,
173     prune_null_branches => 1,
174     inflate_map => $infmap,
175   }))[0],
176   '$_ = {
177       artist => $_->[5], title => $_->[4], year => $_->[2],
178
179       ( single_track => ( (! defined $_->[0] ) && (! defined $_->[1]) && (! defined $_->[3] ) )
180         ? undef
181         : {
182             cd =>
183               {
184                 artist => {
185                     artistid => $_->[1],
186                     ( cds => ( (! defined $_->[0] ) && ( ! defined $_->[3] ) )
187                       ? undef
188                       : {
189                           cdid => $_->[3],
190                           ( tracks => ( ! defined $_->[0] )
191                             ? undef
192                             : { title => $_->[0] }
193                           )
194                         }
195                     )
196                   }
197               }
198           }
199       )
200     } for @{$_[0]}',
201   '1:1 descending non-collapsing HRI-direct parser terminating with chained 1:M:M',
202 );
203
204
205
206 is_deeply (
207   ($schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} })),
208   {
209     -identifying_columns => [ 4, 5 ],
210
211     single_track => {
212       -identifying_columns => [ 1, 4, 5 ],
213       -is_optional => 1,
214       -is_single => 1,
215
216       cd => {
217         -identifying_columns => [ 1, 4, 5 ],
218         -is_single => 1,
219
220         artist => {
221           -identifying_columns => [ 1, 4, 5 ],
222           -is_single => 1,
223
224           cds => {
225             -identifying_columns => [ 1, 3, 4, 5 ],
226             -is_optional => 1,
227
228             tracks => {
229               -identifying_columns => [ 0, 1, 3, 4, 5 ],
230               -is_optional => 1,
231             },
232           },
233         },
234       },
235     },
236   },
237   'Correct collapse map for 1:1 descending chain terminating with chained 1:M:M'
238 );
239
240 is_same_src (
241   ($schema->source ('CD')->_mk_row_parser({
242     inflate_map => $infmap,
243     collapse => 1,
244   }))[0],
245   ' my $rows_pos = 0;
246     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
247
248     while ($cur_row_data = (
249       (
250         $rows_pos >= 0
251           and
252         (
253           $_[0][$rows_pos++]
254             or
255           ( ($rows_pos = -1), undef )
256         )
257       )
258         or
259       ( $_[1] and $_[1]->() )
260     ) ) {
261
262
263       # NULL checks
264       # mandatory => { 4 => 1, 5 => 1 }
265       # from_first_encounter => [ [ 1, 3, 0 ] ]
266       #
267       ( defined( $cur_row_data->[4] ) or $_[3]->{4} = 1 ),
268
269       ( defined( $cur_row_data->[5] ) or $_[3]->{5} = 1 ),
270
271       (
272         ( not defined $cur_row_data->[1] )
273         ? (
274             ( not defined $cur_row_data->[3] )
275               and
276             ( not defined $cur_row_data->[0] )
277               or
278             ( $_[3]->{1} = 1 )
279           )
280       : ( not defined $cur_row_data->[3] )
281         ? (
282             ( not defined $cur_row_data->[0] )
283               or
284             ( $_[3]->{3} = 1 )
285         )
286       : ()
287       ),
288
289       ( keys %{$_[3]} and (
290         ( @{$_[2]} = $cur_row_data ),
291         ( $result_pos = 0 ),
292         last
293       ) ),
294
295
296       ( @cur_row_ids{0,1,3,4,5} = (
297         ( $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0" ),
298         ( $cur_row_data->[1] // "\0NULL\xFF$rows_pos\xFF1\0" ),
299         ( $cur_row_data->[3] // "\0NULL\xFF$rows_pos\xFF3\0" ),
300         ( $cur_row_data->[4] ),
301         ( $cur_row_data->[5] ),
302       ) ),
303
304       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
305       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} and (unshift @{$_[2]}, $cur_row_data) and last ),
306
307       # the rowdata itself for root node
308       ( $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] }] ),
309
310       # prefetch data of single_track (placed in root)
311       ( $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}} = [] ),
312       ( defined($cur_row_data->[1]) or bless( $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{single_track}, __NBC__ ) ),
313
314       # prefetch data of cd (placed in single_track)
315       ( $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}} = [] ),
316
317       # prefetch data of artist ( placed in single_track->cd)
318       ( $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] }] ),
319
320       # prefetch data of cds (if available)
321       (
322         (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
323           and
324         push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cds}}, (
325           $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = [{ cdid => $cur_row_data->[3] }]
326         )
327       ),
328       ( 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__ ) ),
329
330       # prefetch data of tracks (if available)
331       (
332         (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
333           and
334         push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{tracks}}, (
335           $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] }]
336         )
337       ),
338       ( 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__ ) ),
339
340     }
341     $#{$_[0]} = $result_pos - 1;
342   ',
343   'Same 1:1 descending terminating with chained 1:M:M but with collapse',
344 );
345
346 is_same_src (
347   ($schema->source ('CD')->_mk_row_parser({
348     inflate_map => $infmap,
349     collapse => 1,
350     hri_style => 1,
351     prune_null_branches => 1,
352   }))[0],
353   ' my $rows_pos = 0;
354     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
355
356     while ($cur_row_data = (
357       (
358         $rows_pos >= 0
359           and
360         (
361           $_[0][$rows_pos++]
362             or
363           ( ($rows_pos = -1), undef )
364         )
365       )
366         or
367       ( $_[1] and $_[1]->() )
368     ) ) {
369
370
371       # NULL checks
372       # mandatory => { 4 => 1, 5 => 1 }
373       # from_first_encounter => [ [ 1, 3, 0 ] ]
374       #
375       ( defined( $cur_row_data->[4] ) or $_[3]->{4} = 1 ),
376
377       ( defined( $cur_row_data->[5] ) or $_[3]->{5} = 1 ),
378
379       (
380         ( not defined $cur_row_data->[1] )
381         ? (
382           ( not defined $cur_row_data->[3] )
383             and
384           ( not defined $cur_row_data->[0] )
385             or
386           ( $_[3]->{1} = 1 )
387         )
388       : ( not defined $cur_row_data->[3] )
389         ? (
390           ( not defined $cur_row_data->[0] )
391             or
392           ( $_[3]->{3} = 1 )
393         )
394       : ()
395       ),
396
397       ( keys %{$_[3]} and (
398         ( @{$_[2]} = $cur_row_data ),
399         ( $result_pos = 0 ),
400         last
401       ) ),
402
403
404       ( @cur_row_ids{0, 1, 3, 4, 5} = @{$cur_row_data}[0, 1, 3, 4, 5] ),
405
406       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
407       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} and (unshift @{$_[2]}, $cur_row_data) and last ),
408
409       # the rowdata itself for root node
410       ( $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] } ),
411
412       # prefetch data of single_track (placed in root)
413       ( (! defined($cur_row_data->[1]) ) ? $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}{single_track} = undef : do {
414         ( $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}} = {} ),
415
416         # prefetch data of cd (placed in single_track)
417         ( $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}} = {} ),
418
419         # prefetch data of artist ( placed in single_track->cd)
420         ( $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] } ),
421
422         # prefetch data of cds (if available)
423         ( (! defined $cur_row_data->[3] ) ? $collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}{cds} = [] : do {
424
425           (
426             (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
427               and
428             push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}{cds}}, (
429               $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = { cdid => $cur_row_data->[3] }
430             )
431           ),
432
433           # prefetch data of tracks (if available)
434           (( ! defined $cur_row_data->[0] ) ? $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}{tracks} = [] : do {
435
436             (
437               (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
438                 and
439               push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}{tracks}}, (
440                 $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] }
441               )
442             ),
443           } ),
444         } ),
445       } ),
446     }
447     $#{$_[0]} = $result_pos - 1;
448   ',
449   'Same 1:1 descending terminating with chained 1:M:M but with collapse, HRI-direct',
450 );
451
452 $infmap = [qw/
453   tracks.lyrics.existing_lyric_versions.text
454   existing_single_track.cd.artist.artistid
455   existing_single_track.cd.artist.cds.year
456   year
457   genreid
458   tracks.title
459   existing_single_track.cd.artist.cds.cdid
460   latest_cd
461   existing_single_track.cd.artist.cds.tracks.title
462   existing_single_track.cd.artist.cds.genreid
463   tracks.lyrics.existing_lyric_versions.lyric_id
464 /];
465
466 is_deeply (
467   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
468   {
469     -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
470
471     existing_single_track => {
472       -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
473       -is_single => 1,
474
475       cd => {
476         -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
477         -is_single => 1,
478
479         artist => {
480           -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
481           -is_single => 1,
482
483           cds => {
484             -identifying_columns => [ 1, 6 ], # existing_single_track.cd.artist.cds.cdid
485             -is_optional => 1,
486
487             tracks => {
488               -identifying_columns => [ 1, 6, 8 ], # existing_single_track.cd.artist.cds.cdid, existing_single_track.cd.artist.cds.tracks.title
489               -is_optional => 1,
490             }
491           }
492         }
493       }
494     },
495     tracks => {
496       -identifying_columns => [ 1, 5 ], # existing_single_track.cd.artist.artistid, tracks.title
497       -is_optional => 1,
498
499       lyrics => {
500         -identifying_columns => [ 1, 5, 10 ], # existing_single_track.cd.artist.artistid, tracks.title, tracks.lyrics.existing_lyric_versions.lyric_id
501         -is_single => 1,
502         -is_optional => 1,
503
504         existing_lyric_versions => {
505           -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
506         },
507       },
508     }
509   },
510   'Correct collapse map constructed',
511 );
512
513 is_same_src (
514   ($schema->source ('CD')->_mk_row_parser({
515     inflate_map => $infmap,
516     collapse => 1,
517   }))[0],
518   ' my $rows_pos = 0;
519     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
520
521     while ($cur_row_data = (
522       (
523         $rows_pos >= 0
524           and
525         (
526           $_[0][$rows_pos++]
527             or
528           ( ($rows_pos = -1), undef )
529         )
530       )
531         or
532       ( $_[1] and $_[1]->() )
533     ) ) {
534
535
536       # NULL checks
537       # mandatory => { 1 => 1 }
538       # from_first_encounter => [ [6, 8], [5, 10, 0] ],
539       #
540       ( defined( $cur_row_data->[1] ) or $_[3]->{1} = 1 ),
541
542       (
543         ( not defined $cur_row_data->[6] )
544         ? (
545           ( not defined $cur_row_data->[8] )
546             or
547           ( $_[3]->{6} = 1 )
548         )
549       : ()
550       ),
551
552       (
553         ( not defined $cur_row_data->[5] )
554         ? (
555           ( not defined $cur_row_data->[10] )
556             and
557           ( not defined $cur_row_data->[0] )
558             or
559           ( $_[3]->{5} = 1 )
560           )
561       : ( not defined $cur_row_data->[10] )
562         ? (
563           ( not defined $cur_row_data->[0] )
564             or
565           ( $_[3]->{10} = 1 )
566         )
567       : ()
568       ),
569
570       ( keys %{$_[3]} and (
571         ( @{$_[2]} = $cur_row_data ),
572         ( $result_pos = 0 ),
573         last
574       ) ),
575
576
577       ( @cur_row_ids{0, 1, 5, 6, 8, 10} = (
578         $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0",
579         $cur_row_data->[1],
580         $cur_row_data->[5] // "\0NULL\xFF$rows_pos\xFF5\0",
581         $cur_row_data->[6] // "\0NULL\xFF$rows_pos\xFF6\0",
582         $cur_row_data->[8] // "\0NULL\xFF$rows_pos\xFF8\0",
583         $cur_row_data->[10] // "\0NULL\xFF$rows_pos\xFF10\0",
584       ) ),
585
586       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
587       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{1}} and (unshift @{$_[2]}, $cur_row_data) and last ),
588
589       ( $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] }] ),
590
591       ( $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} //= $collapse_idx[1]{$cur_row_ids{1}} = [] ),
592       ( $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} //= $collapse_idx[2]{$cur_row_ids{1}} = [] ),
593       ( $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} //= $collapse_idx[3]{$cur_row_ids{1}} = [{ artistid => $cur_row_data->[1] }] ),
594
595       (
596         (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
597           and
598         push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
599           $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] }]
600         )
601       ),
602       ( defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ ) ),
603
604       (
605         (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
606           and
607         push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
608           $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
609         )
610       ),
611       ( defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ ) ),
612
613       (
614         (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
615           and
616         push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
617           $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
618         )
619       ),
620       ( defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ ) ),
621
622       ( $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}} = [] ),
623       ( defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ ) ),
624
625       (
626         (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
627           and
628         push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
629           $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] }]
630         )
631       ),
632     }
633
634     $#{$_[0]} = $result_pos - 1;
635   ',
636   'Multiple has_many on multiple branches torture test',
637 );
638
639 is_same_src (
640   ($schema->source ('CD')->_mk_row_parser({
641     inflate_map => $infmap,
642     collapse => 1,
643     prune_null_branches => 1,
644   }))[0],
645   ' my $rows_pos = 0;
646     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
647
648     while ($cur_row_data = (
649       (
650         $rows_pos >= 0
651           and
652         (
653           $_[0][$rows_pos++]
654             or
655           ( ($rows_pos = -1), undef )
656         )
657       )
658         or
659       ( $_[1] and $_[1]->() )
660     ) ) {
661
662
663       # NULL checks
664       # mandatory => { 1 => 1 }
665       # from_first_encounter => [ [6, 8], [5, 10, 0] ],
666       #
667       ( defined( $cur_row_data->[1] ) or $_[3]->{1} = 1 ),
668
669       (
670         ( not defined $cur_row_data->[6] )
671         ? (
672           ( not defined $cur_row_data->[8] )
673             or
674           ( $_[3]->{6} = 1 )
675         )
676       : ()
677       ),
678
679       (
680         ( not defined $cur_row_data->[5] )
681         ? (
682           ( not defined $cur_row_data->[10] )
683             and
684           ( not defined $cur_row_data->[0] )
685             or
686           ( $_[3]->{5} = 1 )
687           )
688       : ( not defined $cur_row_data->[10] )
689         ? (
690           ( not defined $cur_row_data->[0] )
691             or
692           ( $_[3]->{10} = 1 )
693         )
694       : ()
695       ),
696
697       ( keys %{$_[3]} and (
698         ( @{$_[2]} = $cur_row_data ),
699         ( $result_pos = 0 ),
700         last
701       ) ),
702
703
704       ( @cur_row_ids{( 0, 1, 5, 6, 8, 10 )} = @{$cur_row_data}[( 0, 1, 5, 6, 8, 10 )] ),
705
706       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
707       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{1}} and (unshift @{$_[2]}, $cur_row_data) and last ),
708
709       ( $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] }] ),
710
711       ( $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} //= $collapse_idx[1]{$cur_row_ids{1}} = [] ),
712       ( $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} //= $collapse_idx[2]{$cur_row_ids{1}} = [] ),
713       ( $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} //= $collapse_idx[3]{$cur_row_ids{1}} = [{ artistid => $cur_row_data->[1] }] ),
714
715       ( (! defined($cur_row_data->[6])) ? $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} = [] : do {
716         (
717           (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
718             and
719           push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
720             $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] }]
721           )
722         ),
723
724         ( (! defined($cur_row_data->[8]) ) ? $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} = [] : do {
725           (
726             (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
727               and
728             push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
729               $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
730             )
731           ),
732         } ),
733       } ),
734
735       ( (! defined($cur_row_data->[5]) ) ? $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} = [] : do {
736
737         (
738           (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
739             and
740           push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
741             $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
742           )
743         ),
744
745         ( (! defined($cur_row_data->[10]) ) ? $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics} = [] : do {
746
747           ( $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}} = [] ),
748
749           (
750             (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
751               and
752             push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
753               $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] }]
754             )
755           ),
756         } ),
757       } ),
758     }
759
760     $#{$_[0]} = $result_pos - 1;
761   ',
762   'Multiple has_many on multiple branches with branch pruning torture test',
763 );
764
765 $infmap = [
766   'single_track.trackid',                   # (0) definitive link to root from 1:1:1:1:M:M chain
767   'year',                                   # (1) non-unique
768   'tracks.cd',                              # (2) \ together both uniqueness for second multirel
769   'tracks.title',                           # (3) / and definitive link back to root
770   'single_track.cd.artist.cds.cdid',        # (4) to give uniquiness to ...tracks.title below
771   'single_track.cd.artist.cds.year',        # (5) non-unique
772   'single_track.cd.artist.artistid',        # (6) uniqufies entire parental chain
773   'single_track.cd.artist.cds.genreid',     # (7) nullable
774   'single_track.cd.artist.cds.tracks.title',# (8) unique when combined with ...cds.cdid above
775 ];
776
777 is_deeply (
778   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
779   {
780     -identifying_columns => [],
781     -identifying_columns_variants => [
782       [ 0 ], [ 2 ],
783     ],
784     single_track => {
785       -identifying_columns => [ 0 ],
786       -is_optional => 1,
787       -is_single => 1,
788       cd => {
789         -identifying_columns => [ 0 ],
790         -is_single => 1,
791         artist => {
792           -identifying_columns => [ 0 ],
793           -is_single => 1,
794           cds => {
795             -identifying_columns => [ 0, 4 ],
796             -is_optional => 1,
797             tracks => {
798               -identifying_columns => [ 0, 4, 8 ],
799               -is_optional => 1,
800             }
801           }
802         }
803       }
804     },
805     tracks => {
806       -identifying_columns => [ 2, 3 ],
807       -is_optional => 1,
808     }
809   },
810   'Correct underdefined root collapse map constructed'
811 );
812
813 is_same_src (
814   ($schema->source ('CD')->_mk_row_parser({
815     inflate_map => $infmap,
816     collapse => 1,
817   }))[0],
818   ' my $rows_pos = 0;
819     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
820
821     while ($cur_row_data = (
822       (
823         $rows_pos >= 0
824           and
825         (
826           $_[0][$rows_pos++]
827             or
828           ( ($rows_pos = -1), undef )
829         )
830       )
831         or
832       ( $_[1] and $_[1]->() )
833     ) ) {
834
835
836       # NULL checks
837       #
838       # from_first_encounter => [ [0, 4, 8] ]
839       # all_or_nothing => [ { 2 => 1, 3 => 1 } ]
840       (
841         ( not defined $cur_row_data->[0] )
842         ? (
843           ( not defined $cur_row_data->[4] )
844             and
845           ( not defined $cur_row_data->[8] )
846             or
847           ( $_[3]->{0} = 1 )
848           )
849       : ( not defined $cur_row_data->[4] )
850         ? (
851           ( not defined $cur_row_data->[8] )
852             or
853           ( $_[3]->{4} = 1 )
854         )
855       : ()
856       ),
857
858       (
859         (
860           ( not defined $cur_row_data->[2] )
861             and
862           ( not defined $cur_row_data->[3] )
863         )
864           or
865         (
866           ( defined($cur_row_data->[2]) or $_[3]->{2} = 1 ),
867           ( defined($cur_row_data->[3]) or $_[3]->{3} = 1 ),
868         )
869       ),
870
871       ( keys %{$_[3]} and (
872         ( @{$_[2]} = $cur_row_data ),
873         ( $result_pos = 0 ),
874         last
875       ) ),
876
877
878       ( @cur_row_ids{( 0, 2, 3, 4, 8 )} = (
879         $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0",
880         $cur_row_data->[2] // "\0NULL\xFF$rows_pos\xFF2\0",
881         $cur_row_data->[3] // "\0NULL\xFF$rows_pos\xFF3\0",
882         $cur_row_data->[4] // "\0NULL\xFF$rows_pos\xFF4\0",
883         $cur_row_data->[8] // "\0NULL\xFF$rows_pos\xFF8\0",
884       )),
885
886       # cache expensive set of ops in a non-existent rowid slot
887       ( $cur_row_ids{10} = (
888         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_ids{0}, q{} ))
889           or
890         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_ids{2}, q{} ))
891           or
892         "\0$rows_pos\0"
893       )),
894
895       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
896       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{10}} and (unshift @{$_[2]}, $cur_row_data) and last ),
897
898       ( $collapse_idx[0]{$cur_row_ids{10}} //= $_[0][$result_pos++] = [{ year => $$cur_row_data[1] }] ),
899
900       ( $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track} //= ($collapse_idx[1]{$cur_row_ids{0}} = [{ trackid => $cur_row_data->[0] }]) ),
901       ( defined($cur_row_data->[0]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track}, __NBC__ ) ),
902
903       ( $collapse_idx[1]{$cur_row_ids{0}}[1]{cd} //= $collapse_idx[2]{$cur_row_ids{0}} = [] ),
904
905       ( $collapse_idx[2]{$cur_row_ids{0}}[1]{artist} //= ($collapse_idx[3]{$cur_row_ids{0}} = [{ artistid => $cur_row_data->[6] }]) ),
906
907       (
908         (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
909           and
910         push @{$collapse_idx[3]{$cur_row_ids{0}}[1]{cds}}, (
911           $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] }]
912         )
913       ),
914       ( defined($cur_row_data->[4]) or bless ( $collapse_idx[3]{$cur_row_ids{0}}[1]{cds}, __NBC__ ) ),
915
916       (
917         (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
918           and
919         push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}}, (
920             $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
921         )
922       ),
923       ( defined($cur_row_data->[8]) or bless ( $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}, __NBC__ ) ),
924
925       (
926         (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
927           and
928         push @{$collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}}, (
929           $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = [{ cd => $$cur_row_data[2], title => $cur_row_data->[3] }]
930         )
931       ),
932       ( defined($cur_row_data->[2]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}, __NBC__ ) ),
933     }
934
935     $#{$_[0]} = $result_pos - 1;
936   ',
937   'Multiple has_many on multiple branches with underdefined root torture test',
938 );
939
940 is_same_src (
941   ($schema->source ('CD')->_mk_row_parser({
942     inflate_map => $infmap,
943     collapse => 1,
944     hri_style => 1,
945     prune_null_branches => 1,
946   }))[0],
947   ' my $rows_pos = 0;
948     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
949
950     while ($cur_row_data = (
951       (
952         $rows_pos >= 0
953           and
954         (
955           $_[0][$rows_pos++]
956             or
957           ( ($rows_pos = -1), undef )
958         )
959       )
960         or
961       ( $_[1] and $_[1]->() )
962     ) ) {
963
964
965       # NULL checks
966       #
967       # from_first_encounter => [ [0, 4, 8] ]
968       # all_or_nothing => [ { 2 => 1, 3 => 1 } ]
969       (
970         ( not defined $cur_row_data->[0] )
971         ? (
972           ( not defined $cur_row_data->[4] )
973             and
974           ( not defined $cur_row_data->[8] )
975             or
976           ( $_[3]->{0} = 1 )
977           )
978       : ( not defined $cur_row_data->[4] )
979         ? (
980           ( not defined $cur_row_data->[8] )
981             or
982           ( $_[3]->{4} = 1 )
983         )
984       : ()
985       ),
986
987       (
988         (
989           ( not defined $cur_row_data->[2] )
990             and
991           ( not defined $cur_row_data->[3] )
992         )
993           or
994         (
995           ( defined($cur_row_data->[2]) or $_[3]->{2} = 1 ),
996           ( defined($cur_row_data->[3]) or $_[3]->{3} = 1 ),
997         )
998       ),
999
1000       ( keys %{$_[3]} and (
1001         ( @{$_[2]} = $cur_row_data ),
1002         ( $result_pos = 0 ),
1003         last
1004       ) ),
1005
1006
1007       # do not care about nullability here
1008       ( @cur_row_ids{( 0, 2, 3, 4, 8 )} = @{$cur_row_data}[( 0, 2, 3, 4, 8 )] ),
1009
1010       # cache expensive set of ops in a non-existent rowid slot
1011       ( $cur_row_ids{10} = (
1012         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_ids{0}, q{} ))
1013           or
1014         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_ids{2}, q{} ))
1015           or
1016         "\0$rows_pos\0"
1017       )),
1018
1019       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
1020       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{10}} and (unshift @{$_[2]}, $cur_row_data) and last ),
1021
1022       ( $collapse_idx[0]{$cur_row_ids{10}} //= $_[0][$result_pos++] = { year => $$cur_row_data[1] } ),
1023
1024       ( (! defined $cur_row_data->[0] ) ? $collapse_idx[0]{$cur_row_ids{10}}{single_track} = undef : do {
1025
1026         ( $collapse_idx[0]{$cur_row_ids{10}}{single_track} //= ($collapse_idx[1]{$cur_row_ids{0}} = { trackid => $$cur_row_data[0] }) ),
1027
1028         ( $collapse_idx[1]{$cur_row_ids{0}}{cd} //= $collapse_idx[2]{$cur_row_ids{0}} = {} ),
1029
1030         ( $collapse_idx[2]{$cur_row_ids{0}}{artist} //= ($collapse_idx[3]{$cur_row_ids{0}} = { artistid => $$cur_row_data[6] }) ),
1031
1032         ( (! defined $cur_row_data->[4] ) ? $collapse_idx[3]{$cur_row_ids{0}}{cds} = [] : do {
1033
1034           (
1035             (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
1036               and
1037             push @{$collapse_idx[3]{$cur_row_ids{0}}{cds}}, (
1038                 $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] }
1039             )
1040           ),
1041
1042           ( (! defined $cur_row_data->[8] ) ? $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}{tracks} = [] : do {
1043
1044             (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
1045               and
1046             push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}{tracks}}, (
1047                 $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = { title => $$cur_row_data[8] }
1048             ),
1049           } ),
1050         } ),
1051       } ),
1052
1053       ( (! defined $cur_row_data->[2] ) ? $collapse_idx[0]{$cur_row_ids{10}}{tracks} = [] : do {
1054         (
1055           (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
1056             and
1057           push @{$collapse_idx[0]{$cur_row_ids{10}}{tracks}}, (
1058             $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = { cd => $$cur_row_data[2], title => $$cur_row_data[3] }
1059           )
1060         ),
1061       } ),
1062     }
1063
1064     $#{$_[0]} = $result_pos - 1;
1065   ',
1066   'Multiple has_many on multiple branches with underdefined root, HRI-direct torture test',
1067 );
1068
1069
1070 $infmap = [
1071   'single_track.lyrics.track_id',             # (0) random optional 1:1:1 chain
1072   'year',                                     # (1) non-unique
1073   'tracks.cd',                                # (2) \ together both uniqueness for second multirel
1074   'tracks.title',                             # (3) / and definitive link back to root
1075   'single_track.cd.artist.cds.cdid',          # (4) to give uniquiness to ...tracks.title below
1076   'single_track.cd.artist.cds.year',          # (5) non-unique
1077   'single_track.cd.artist.artistid',          # (6) uniqufies entire parental chain
1078   'single_track.cd.artist.cds.genreid',       # (7) nullable
1079   'single_track.cd.artist.cds.tracks.title',  # (8) unique when combined with ...cds.cdid above
1080   'single_track.lyrics.lyric_versions.text',  # (9) unique combined with the single_track.lyrics 1:1:1
1081 ];
1082
1083 is_deeply (
1084   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
1085   {
1086     -identifying_columns => [],
1087     -identifying_columns_variants => [
1088       [ 2 ], [ 6 ],
1089     ],
1090     single_track => {
1091       -identifying_columns => [ 6 ],
1092       -is_optional => 1,
1093       -is_single => 1,
1094       cd => {
1095         -identifying_columns => [ 6 ],
1096         -is_single => 1,
1097         artist => {
1098           -identifying_columns => [ 6 ],
1099           -is_single => 1,
1100           cds => {
1101             -identifying_columns => [ 4, 6 ],
1102             -is_optional => 1,
1103             tracks => {
1104               -identifying_columns => [ 4, 6, 8 ],
1105               -is_optional => 1,
1106             }
1107           }
1108         }
1109       },
1110       lyrics => {
1111         -identifying_columns => [ 0, 6 ],
1112         -is_optional => 1,
1113         -is_single => 1,
1114         lyric_versions => {
1115           -identifying_columns => [ 0, 6, 9 ],
1116           -is_optional => 1,
1117         },
1118       },
1119     },
1120     tracks => {
1121       -identifying_columns => [ 2, 3 ],
1122       -is_optional => 1,
1123     }
1124   },
1125   'Correct underdefined root tripple-has-many-torture collapse map constructed'
1126 );
1127
1128 is_same_src (
1129   ($schema->source ('CD')->_mk_row_parser({
1130     inflate_map => $infmap,
1131     collapse => 1,
1132     hri_style => 1,
1133     prune_null_branches => 1,
1134   }))[0],
1135   ' my $rows_pos = 0;
1136     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
1137
1138     while ($cur_row_data = (
1139       (
1140         $rows_pos >= 0
1141           and
1142         (
1143           $_[0][$rows_pos++]
1144             or
1145           ( ($rows_pos = -1), undef )
1146         )
1147       )
1148         or
1149       ( $_[1] and $_[1]->() )
1150     ) ) {
1151
1152       # NULL checks
1153       #
1154       # from_first_encounter => [ [6, 4, 8], [6, 0, 9] ]
1155       # all_or_nothing => [ { 2 => 1, 3 => 1 } ]
1156       (
1157         ( not defined $cur_row_data->[6] )
1158         ? (
1159           ( not defined $cur_row_data->[4] )
1160             and
1161           ( not defined $cur_row_data->[8] )
1162             or
1163           ( $_[3]->{6} = 1 )
1164           )
1165       : ( not defined $cur_row_data->[4] )
1166         ? (
1167           ( not defined $cur_row_data->[8] )
1168             or
1169           ( $_[3]->{4} = 1 )
1170           )
1171       : ()
1172       ),
1173
1174       (
1175         ( not defined $cur_row_data->[6] )
1176         ? (
1177           ( not defined $cur_row_data->[0] )
1178             and
1179           ( not defined $cur_row_data->[9] )
1180             or
1181           ( $_[3]->{6} = 1 )
1182           )
1183       : ( not defined $cur_row_data->[0] )
1184         ? (
1185           ( not defined $cur_row_data->[9] )
1186             or
1187           ( $_[3]->{0} = 1 )
1188           )
1189       : ()
1190       ),
1191
1192       (
1193         (
1194           ( not defined $cur_row_data->[2] )
1195             and
1196           ( not defined $cur_row_data->[3] )
1197         )
1198           or
1199         (
1200           ( defined($cur_row_data->[2]) or $_[3]->{2} = 1 ),
1201           ( defined($cur_row_data->[3]) or $_[3]->{3} = 1 ),
1202         )
1203       ),
1204
1205       ( keys %{$_[3]} and (
1206         ( @{$_[2]} = $cur_row_data ),
1207         ( $result_pos = 0 ),
1208         last
1209       ) ),
1210
1211
1212       # do not care about nullability here
1213       ( @cur_row_ids{( 0, 2, 3, 4, 6, 8, 9 )} = @{$cur_row_data}[( 0, 2, 3, 4, 6, 8, 9 )] ),
1214
1215       # cache expensive set of ops in a non-existent rowid slot
1216       ( $cur_row_ids{11} = (
1217         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_ids{2}, q{} ))
1218           or
1219         ( ( defined $cur_row_data->[6] ) && (join "\xFF", q{}, $cur_row_ids{6}, q{} ))
1220           or
1221         "\0$rows_pos\0"
1222       )),
1223
1224       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
1225       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{11}} and (unshift @{$_[2]}, $cur_row_data) and last ),
1226
1227       ( $collapse_idx[0]{$cur_row_ids{11}} //= $_[0][$result_pos++] = { year => $$cur_row_data[1] } ),
1228
1229       ( (! defined $cur_row_data->[6] ) ? $collapse_idx[0]{$cur_row_ids{11}}{single_track} = undef : do {
1230
1231         ( $collapse_idx[0]{$cur_row_ids{11}}{single_track} //= ( $collapse_idx[1]{$cur_row_ids{6}} = {} ) ),
1232
1233         ( $collapse_idx[1]{$cur_row_ids{6}}{cd} //= $collapse_idx[2]{$cur_row_ids{6}} = {} ),
1234
1235         ( $collapse_idx[2]{$cur_row_ids{6}}{artist} //= ($collapse_idx[3]{$cur_row_ids{6}} = { artistid => $$cur_row_data[6] }) ),
1236
1237         ( (! defined $cur_row_data->[4] ) ? $collapse_idx[3]{$cur_row_ids{6}}{cds} = [] : do {
1238
1239           (
1240             (! $collapse_idx[4]{$cur_row_ids{4}}{$cur_row_ids{6}} )
1241               and
1242             push @{$collapse_idx[3]{$cur_row_ids{6}}{cds}}, (
1243                 $collapse_idx[4]{$cur_row_ids{4}}{$cur_row_ids{6}} = { cdid => $$cur_row_data[4], genreid => $$cur_row_data[7], year => $$cur_row_data[5] }
1244             )
1245           ),
1246
1247           ( (! defined $cur_row_data->[8] ) ? $collapse_idx[4]{$cur_row_ids{4}}{$cur_row_ids{6}}{tracks} = [] : do {
1248
1249             (! $collapse_idx[5]{$cur_row_ids{4}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
1250               and
1251             push @{$collapse_idx[4]{$cur_row_ids{4}}{$cur_row_ids{6}}{tracks}}, (
1252                 $collapse_idx[5]{$cur_row_ids{4}}{$cur_row_ids{6}}{$cur_row_ids{8}} = { title => $$cur_row_data[8] }
1253             ),
1254           } ),
1255         } ),
1256
1257         ( ( ! defined $cur_row_data->[0] ) ? $collapse_idx[1]{ $cur_row_ids{6} }{"lyrics"} = undef : do {
1258
1259           ( $collapse_idx[1]{ $cur_row_ids{6} }{"lyrics"} //= ( $collapse_idx[6]{ $cur_row_ids{0} }{ $cur_row_ids{6} } = { "track_id" => $cur_row_data->[0] } ) ),
1260
1261           ( ( ! defined $cur_row_data->[9] ) ? $collapse_idx[6]{ $cur_row_ids{0} }{ $cur_row_ids{6} }{"lyric_versions"} = [] : do {
1262             (
1263               (! $collapse_idx[7]{ $cur_row_ids{0} }{ $cur_row_ids{6} }{ $cur_row_ids{9} })
1264                 and
1265               push @{$collapse_idx[6]{ $cur_row_ids{0} }{ $cur_row_ids{6} }{"lyric_versions"}}, (
1266                 $collapse_idx[7]{ $cur_row_ids{0} }{ $cur_row_ids{6} }{ $cur_row_ids{9} } = { "text" => $cur_row_data->[9] }
1267               ),
1268             ),
1269           } ),
1270         } ),
1271       } ),
1272
1273       ( (! defined $cur_row_data->[2] ) ? $collapse_idx[0]{$cur_row_ids{11}}{tracks} = [] : do {
1274         (
1275           (! $collapse_idx[8]{$cur_row_ids{2}}{$cur_row_ids{3}} )
1276             and
1277           push @{$collapse_idx[0]{$cur_row_ids{11}}{tracks}}, (
1278             $collapse_idx[8]{$cur_row_ids{2}}{$cur_row_ids{3}} = { cd => $$cur_row_data[2], title => $$cur_row_data[3] }
1279           )
1280         ),
1281       } ),
1282     }
1283
1284     $#{$_[0]} = $result_pos - 1;
1285   ',
1286   'Tripple multiple has_many on multiple branches with underdefined root, HRI-direct torture test',
1287 );
1288
1289 is_same_src (
1290   ($schema->source ('Owners')->_mk_row_parser({
1291     inflate_map => [qw( books.title books.owner )],
1292     collapse => 1,
1293     prune_null_branches => 1,
1294   }))[0],
1295   ' my $rows_pos = 0;
1296     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids );
1297
1298     while ($cur_row_data = (
1299       (
1300         $rows_pos >= 0
1301           and
1302         (
1303           $_[0][$rows_pos++]
1304             or
1305           ( ($rows_pos = -1), undef )
1306         )
1307       )
1308         or
1309       ( $_[1] and $_[1]->() )
1310     ) ) {
1311
1312       ( @cur_row_ids{0,1} = @{$cur_row_data}[0,1] ),
1313
1314       ( $cur_row_ids{3} = (
1315         ( ( defined $cur_row_data->[1] ) && (join "\xFF", q{}, $cur_row_ids{1}, q{} ))
1316           or
1317         "\0${rows_pos}\0"
1318       )),
1319
1320       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{3}} and (unshift @{$_[2]}, $cur_row_data) and last ),
1321
1322       # empty data for the root node
1323       ( $collapse_idx[0]{$cur_row_ids{3}} //= $_[0][$result_pos++] = [] ),
1324
1325       ( ( ! defined $cur_row_data->[0] ) ? $collapse_idx[0]{$cur_row_ids{3}}[1]{"books"} = [] : do {
1326         ( ! $collapse_idx[1]{$cur_row_ids{0}} )
1327           and
1328         push @{$collapse_idx[0]{$cur_row_ids{3}}[1]{books}},
1329           $collapse_idx[1]{$cur_row_ids{0}} = [ { owner => $cur_row_data->[1], title => $cur_row_data->[0] } ]
1330       } ),
1331     }
1332
1333     $#{$_[0]} = $result_pos - 1; # truncate the passed in array to where we filled it with results
1334   ',
1335   'Non-premultiplied implicit collapse with missing join columns',
1336 );
1337
1338 is_same_src (
1339   ($schema->source('Artist')->_mk_row_parser({
1340     inflate_map => [qw( artistid cds.artist cds.title cds.tracks.title )],
1341     collapse => 1,
1342     prune_null_branches => 1,
1343   }))[0],
1344   ' my $rows_pos = 0;
1345     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids );
1346
1347     while ($cur_row_data = (
1348       (
1349         $rows_pos >= 0
1350           and
1351         (
1352           $_[0][$rows_pos++]
1353             or
1354           ( ($rows_pos = -1), undef )
1355         )
1356       )
1357         or
1358       ( $_[1] and $_[1]->() )
1359     ) ) {
1360
1361       # NULL checks
1362       #
1363       # mandatory => { 0 => 1 }
1364       # from_first_encounter => [ [1, 2, 3] ]
1365       # all_or_nothing => [ { 1 => 1, 2 => 1 } ]
1366
1367       ( defined( $cur_row_data->[0] ) or $_[3]->{0} = 1 ),
1368
1369       (
1370         ( not defined $cur_row_data->[1] )
1371         ? (
1372           ( not defined $cur_row_data->[2] )
1373             and
1374           ( not defined $cur_row_data->[3] )
1375             or
1376           $_[3]->{1} = 1
1377           )
1378       : ( not defined $cur_row_data->[2] )
1379         ? (
1380           ( not defined $cur_row_data->[3] )
1381             or
1382           $_[3]->{2} = 1
1383           )
1384       : ()
1385       ),
1386
1387       (
1388         (
1389           ( not defined $cur_row_data->[1] )
1390             and
1391           ( not defined $cur_row_data->[2] )
1392         )
1393           or
1394         (
1395           ( defined($cur_row_data->[1]) or $_[3]->{1} = 1 ),
1396           ( defined($cur_row_data->[2]) or $_[3]->{2} = 1 ),
1397         )
1398       ),
1399
1400       ( keys %{$_[3]} and (
1401         ( @{$_[2]} = $cur_row_data ),
1402         ( $result_pos = 0 ),
1403         last
1404       ) ),
1405
1406
1407       ( @cur_row_ids{( 0, 1, 2, 3 )} = @{$cur_row_data}[ 0, 1, 2, 3 ] ),
1408
1409       ( $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{0}} and (unshift @{$_[2]}, $cur_row_data) and last ),
1410
1411       ( $collapse_idx[0]{ $cur_row_ids{0} }
1412           //= $_[0][$result_pos++] = [ { "artistid" => $cur_row_data->[0] } ]
1413       ),
1414
1415       ( ( ! defined $cur_row_data->[1] ) ? $collapse_idx[0]{ $cur_row_ids{0} }[1]{"cds"} = [] : do {
1416
1417         (
1418           ! $collapse_idx[1]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }
1419             and
1420           push @{$collapse_idx[0]{ $cur_row_ids{0} }[1]{"cds"}},
1421             $collapse_idx[1]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }
1422               = [ { "artist" => $cur_row_data->[1], "title" => $cur_row_data->[2] } ]
1423         ),
1424
1425         ( ( ! defined $cur_row_data->[3] ) ? $collapse_idx[1]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }[1]{"tracks"} = [] : do {
1426           (
1427             ! $collapse_idx[2]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }{ $cur_row_ids{3} }
1428               and
1429             push @{$collapse_idx[1]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }[1]{"tracks"}},
1430               $collapse_idx[2]{ $cur_row_ids{0} }{ $cur_row_ids{1} }{ $cur_row_ids{2} }{ $cur_row_ids{3} }
1431                 = [ { "title" => $cur_row_data->[3] } ]
1432           ),
1433         } ),
1434       } ),
1435     }
1436
1437     $#{$_[0]} = $result_pos - 1
1438   ',
1439   'A rolled out version of inflate map of misled_rowparser.t'
1440 );
1441
1442 done_testing;
1443
1444 my $deparser;
1445 sub is_same_src { SKIP: {
1446
1447   skip "Skipping comparison of unicode-poisoned source", 1
1448     if DBIx::Class::_ENV_::STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE;
1449
1450   $deparser ||= B::Deparse->new;
1451   local $Test::Builder::Level = $Test::Builder::Level + 1;
1452
1453   my ($got, $expect) = @_;
1454
1455   skip "Not testing equality of source containing defined-or operator on this perl $]", 1
1456     if ( "$]" < 5.010 and $expect =~ m!\Q//=! );
1457
1458   $expect =~ s/__NBC__/perlstring($DBIx::Class::ResultSource::RowParser::Util::null_branch_class)/ge;
1459
1460   $expect = "sub { use strict; use warnings FATAL => 'uninitialized';\n$expect\n  }";
1461
1462   my @normalized = map {
1463     my $cref = eval "sub { $_ }" or do {
1464       fail "Coderef does not compile!\n\n$@\n\n$_";
1465       return undef;
1466     };
1467     $deparser->coderef2text($cref);
1468   } ($got, $expect);
1469
1470   &is (@normalized, $_[2]||() ) or do {
1471     eval { require Test::Differences }
1472       ? &Test::Differences::eq_or_diff( @normalized, $_[2]||() )
1473       : note ("Original sources:\n\n$got\n\n$expect\n")
1474     ;
1475     exit 1;
1476   };
1477 } }