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