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