Rewrite the collapsing rowparser pigeonholing logic
[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,1,3,4,5} = (
261         ( $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0" ),
262         ( $cur_row_data->[1] // "\0NULL\xFF$rows_pos\xFF1\0" ),
263         ( $cur_row_data->[3] // "\0NULL\xFF$rows_pos\xFF3\0" ),
264         ( $cur_row_data->[4] // "\0NULL\xFF$rows_pos\xFF4\0" ),
265         ( $cur_row_data->[5] // "\0NULL\xFF$rows_pos\xFF5\0" ),
266       );
267
268       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
269       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} and (unshift @{$_[2]}, $cur_row_data) and last;
270
271       # the rowdata itself for root node
272       $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] }];
273
274       # prefetch data of single_track (placed in root)
275       $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}} = [];
276       defined($cur_row_data->[1]) or bless( $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{single_track}, __NBC__ );
277
278       # prefetch data of cd (placed in single_track)
279       $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}} = [];
280
281       # prefetch data of artist ( placed in single_track->cd)
282       $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] }];
283
284       # prefetch data of cds (if available)
285       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
286         and
287       push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{cds}}, (
288         $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = [{ cdid => $cur_row_data->[3] }]
289       );
290       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__ );
291
292       # prefetch data of tracks (if available)
293       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
294         and
295       push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}[1]{tracks}}, (
296         $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] }]
297       );
298       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__ );
299
300     }
301     $#{$_[0]} = $result_pos - 1;
302   ',
303   'Same 1:1 descending terminating with chained 1:M:M but with collapse',
304 );
305
306 is_same_src (
307   ($schema->source ('CD')->_mk_row_parser({
308     inflate_map => $infmap,
309     collapse => 1,
310     hri_style => 1,
311     prune_null_branches => 1,
312   }))[0],
313   ' my $rows_pos = 0;
314     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
315
316     while ($cur_row_data = (
317       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
318         ||
319       ( $_[1] and $rows_pos = -1 and $_[1]->() )
320     ) ) {
321
322       @cur_row_ids{0, 1, 3, 4, 5} = @{$cur_row_data}[0, 1, 3, 4, 5];
323
324       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
325       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}} and (unshift @{$_[2]}, $cur_row_data) and last;
326
327       # the rowdata itself for root node
328       $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] };
329
330       # prefetch data of single_track (placed in root)
331       (! defined($cur_row_data->[1]) ) ? $collapse_idx[0]{$cur_row_ids{4}}{$cur_row_ids{5}}{single_track} = undef : do {
332         $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}};
333
334         # prefetch data of cd (placed in single_track)
335         $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}};
336
337         # prefetch data of artist ( placed in single_track->cd)
338         $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] };
339
340         # prefetch data of cds (if available)
341         (! defined $cur_row_data->[3] ) ? $collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}{cds} = [] : do {
342
343           (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
344             and
345           push @{$collapse_idx[3]{$cur_row_ids{1}}{$cur_row_ids{4}}{$cur_row_ids{5}}{cds}}, (
346             $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} = { cdid => $cur_row_data->[3] }
347           );
348
349           # prefetch data of tracks (if available)
350           ( ! 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 {
351
352             (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}} )
353               and
354             push @{$collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{3}}{$cur_row_ids{4}}{$cur_row_ids{5}}{tracks}}, (
355               $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] }
356             );
357           };
358         };
359       };
360     }
361     $#{$_[0]} = $result_pos - 1;
362   ',
363   'Same 1:1 descending terminating with chained 1:M:M but with collapse, HRI-direct',
364 );
365
366 $infmap = [qw/
367   tracks.lyrics.existing_lyric_versions.text
368   existing_single_track.cd.artist.artistid
369   existing_single_track.cd.artist.cds.year
370   year
371   genreid
372   tracks.title
373   existing_single_track.cd.artist.cds.cdid
374   latest_cd
375   existing_single_track.cd.artist.cds.tracks.title
376   existing_single_track.cd.artist.cds.genreid
377   tracks.lyrics.existing_lyric_versions.lyric_id
378 /];
379
380 is_deeply (
381   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
382   {
383     -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
384
385     existing_single_track => {
386       -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
387       -is_single => 1,
388
389       cd => {
390         -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
391         -is_single => 1,
392
393         artist => {
394           -identifying_columns => [ 1 ], # existing_single_track.cd.artist.artistid
395           -is_single => 1,
396
397           cds => {
398             -identifying_columns => [ 1, 6 ], # existing_single_track.cd.artist.cds.cdid
399             -is_optional => 1,
400
401             tracks => {
402               -identifying_columns => [ 1, 6, 8 ], # existing_single_track.cd.artist.cds.cdid, existing_single_track.cd.artist.cds.tracks.title
403               -is_optional => 1,
404             }
405           }
406         }
407       }
408     },
409     tracks => {
410       -identifying_columns => [ 1, 5 ], # existing_single_track.cd.artist.artistid, tracks.title
411       -is_optional => 1,
412
413       lyrics => {
414         -identifying_columns => [ 1, 5, 10 ], # existing_single_track.cd.artist.artistid, tracks.title, tracks.lyrics.existing_lyric_versions.lyric_id
415         -is_single => 1,
416         -is_optional => 1,
417
418         existing_lyric_versions => {
419           -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
420         },
421       },
422     }
423   },
424   'Correct collapse map constructed',
425 );
426
427 is_same_src (
428   ($schema->source ('CD')->_mk_row_parser({
429     inflate_map => $infmap,
430     collapse => 1,
431   }))[0],
432   ' my $rows_pos = 0;
433     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
434
435     while ($cur_row_data = (
436       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
437         ||
438       ( $_[1] and $rows_pos = -1 and $_[1]->() )
439     ) ) {
440
441       @cur_row_ids{0, 1, 5, 6, 8, 10} = (
442         $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0",
443         $cur_row_data->[1] // "\0NULL\xFF$rows_pos\xFF1\0",
444         $cur_row_data->[5] // "\0NULL\xFF$rows_pos\xFF5\0",
445         $cur_row_data->[6] // "\0NULL\xFF$rows_pos\xFF6\0",
446         $cur_row_data->[8] // "\0NULL\xFF$rows_pos\xFF8\0",
447         $cur_row_data->[10] // "\0NULL\xFF$rows_pos\xFF10\0",
448       );
449
450       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
451       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{1}} and (unshift @{$_[2]}, $cur_row_data) and last;
452
453       $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] }];
454
455       $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} //= $collapse_idx[1]{$cur_row_ids{1}} = [];
456       $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} //= $collapse_idx[2]{$cur_row_ids{1}} = [];
457       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} //= $collapse_idx[3]{$cur_row_ids{1}} = [{ artistid => $cur_row_data->[1] }];
458
459       (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
460         and
461       push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
462         $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] }]
463       );
464       defined($cur_row_data->[6]) or bless( $collapse_idx[3]{$cur_row_ids{1}}[1]{cds}, __NBC__ );
465
466       (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
467         and
468       push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
469         $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
470       );
471       defined($cur_row_data->[8]) or bless( $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks}, __NBC__ );
472
473       (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
474         and
475       push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
476         $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
477       );
478       defined($cur_row_data->[5]) or bless( $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks}, __NBC__ );
479
480       $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}} = [];
481       defined($cur_row_data->[10]) or bless( $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics}, __NBC__ );
482
483       (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
484         and
485       push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
486         $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] }]
487       );
488     }
489
490     $#{$_[0]} = $result_pos - 1;
491   ',
492   'Multiple has_many on multiple branches torture test',
493 );
494
495 is_same_src (
496   ($schema->source ('CD')->_mk_row_parser({
497     inflate_map => $infmap,
498     collapse => 1,
499     prune_null_branches => 1,
500   }))[0],
501   ' my $rows_pos = 0;
502     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
503
504     while ($cur_row_data = (
505       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
506         ||
507       ( $_[1] and $rows_pos = -1 and $_[1]->() )
508     ) ) {
509
510       @cur_row_ids{( 0, 1, 5, 6, 8, 10 )} = @{$cur_row_data}[( 0, 1, 5, 6, 8, 10 )];
511
512       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
513       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{1}} and (unshift @{$_[2]}, $cur_row_data) and last;
514
515       $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] }];
516
517       $collapse_idx[0]{$cur_row_ids{1}}[1]{existing_single_track} //= $collapse_idx[1]{$cur_row_ids{1}} = [];
518       $collapse_idx[1]{$cur_row_ids{1}}[1]{cd} //= $collapse_idx[2]{$cur_row_ids{1}} = [];
519       $collapse_idx[2]{$cur_row_ids{1}}[1]{artist} //= $collapse_idx[3]{$cur_row_ids{1}} = [{ artistid => $cur_row_data->[1] }];
520
521       (! defined($cur_row_data->[6])) ? $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} = [] : do {
522         (! $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}} )
523           and
524         push @{ $collapse_idx[3]{$cur_row_ids{1}}[1]{cds} }, (
525           $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] }]
526         );
527
528         (! defined($cur_row_data->[8]) ) ? $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} = [] : do {
529
530           (! $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} )
531             and
532           push @{ $collapse_idx[4]{$cur_row_ids{1}}{$cur_row_ids{6}}[1]{tracks} }, (
533             $collapse_idx[5]{$cur_row_ids{1}}{$cur_row_ids{6}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
534           );
535         };
536       };
537
538       (! defined($cur_row_data->[5]) ) ? $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} = [] : do {
539
540         (! $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} )
541           and
542         push @{ $collapse_idx[0]{$cur_row_ids{1}}[1]{tracks} }, (
543           $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}} = [{ title => $cur_row_data->[5] }]
544         );
545
546         (! defined($cur_row_data->[10]) ) ? $collapse_idx[6]{$cur_row_ids{1}}{$cur_row_ids{5}}[1]{lyrics} = [] : do {
547
548           $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}} = [];
549
550           (! $collapse_idx[8]{$cur_row_ids{0}}{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}} )
551             and
552           push @{ $collapse_idx[7]{$cur_row_ids{1}}{$cur_row_ids{5}}{$cur_row_ids{10}}[1]{existing_lyric_versions} }, (
553             $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] }]
554           );
555         };
556       };
557     }
558
559     $#{$_[0]} = $result_pos - 1;
560   ',
561   'Multiple has_many on multiple branches with branch pruning torture test',
562 );
563
564 $infmap = [
565   'single_track.trackid',                   # (0) definitive link to root from 1:1:1:1:M:M chain
566   'year',                                   # (1) non-unique
567   'tracks.cd',                              # (2) \ together both uniqueness for second multirel
568   'tracks.title',                           # (3) / and definitive link back to root
569   'single_track.cd.artist.cds.cdid',        # (4) to give uniquiness to ...tracks.title below
570   'single_track.cd.artist.cds.year',        # (5) non-unique
571   'single_track.cd.artist.artistid',        # (6) uniqufies entire parental chain
572   'single_track.cd.artist.cds.genreid',     # (7) nullable
573   'single_track.cd.artist.cds.tracks.title',# (8) unique when combined with ...cds.cdid above
574 ];
575
576 is_deeply (
577   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
578   {
579     -identifying_columns => [],
580     -identifying_columns_variants => [
581       [ 0 ], [ 2 ],
582     ],
583     single_track => {
584       -identifying_columns => [ 0 ],
585       -is_optional => 1,
586       -is_single => 1,
587       cd => {
588         -identifying_columns => [ 0 ],
589         -is_single => 1,
590         artist => {
591           -identifying_columns => [ 0 ],
592           -is_single => 1,
593           cds => {
594             -identifying_columns => [ 0, 4 ],
595             -is_optional => 1,
596             tracks => {
597               -identifying_columns => [ 0, 4, 8 ],
598               -is_optional => 1,
599             }
600           }
601         }
602       }
603     },
604     tracks => {
605       -identifying_columns => [ 2, 3 ],
606       -is_optional => 1,
607     }
608   },
609   'Correct underdefined root collapse map constructed'
610 );
611
612 is_same_src (
613   ($schema->source ('CD')->_mk_row_parser({
614     inflate_map => $infmap,
615     collapse => 1,
616   }))[0],
617   ' my $rows_pos = 0;
618     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
619
620     while ($cur_row_data = (
621       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
622         ||
623       ( $_[1] and $rows_pos = -1 and $_[1]->() )
624     ) ) {
625
626       @cur_row_ids{( 0, 2, 3, 4, 8 )} = (
627         $cur_row_data->[0] // "\0NULL\xFF$rows_pos\xFF0\0",
628         $cur_row_data->[2] // "\0NULL\xFF$rows_pos\xFF2\0",
629         $cur_row_data->[3] // "\0NULL\xFF$rows_pos\xFF3\0",
630         $cur_row_data->[4] // "\0NULL\xFF$rows_pos\xFF4\0",
631         $cur_row_data->[8] // "\0NULL\xFF$rows_pos\xFF8\0",
632       );
633
634       # cache expensive set of ops in a non-existent rowid slot
635       $cur_row_ids{10} = (
636         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_ids{0}, q{} ))
637           or
638         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_ids{2}, q{} ))
639           or
640         "\0$rows_pos\0"
641       );
642
643       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
644       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{10}} and (unshift @{$_[2]}, $cur_row_data) and last;
645
646       $collapse_idx[0]{$cur_row_ids{10}} //= $_[0][$result_pos++] = [{ year => $$cur_row_data[1] }];
647
648       $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track} //= ($collapse_idx[1]{$cur_row_ids{0}} = [{ trackid => $cur_row_data->[0] }]);
649       defined($cur_row_data->[0]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{single_track}, __NBC__ );
650
651       $collapse_idx[1]{$cur_row_ids{0}}[1]{cd} //= $collapse_idx[2]{$cur_row_ids{0}} = [];
652
653       $collapse_idx[2]{$cur_row_ids{0}}[1]{artist} //= ($collapse_idx[3]{$cur_row_ids{0}} = [{ artistid => $cur_row_data->[6] }]);
654
655       (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
656         and
657       push @{$collapse_idx[3]{$cur_row_ids{0}}[1]{cds}}, (
658           $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] }]
659       );
660       defined($cur_row_data->[4]) or bless ( $collapse_idx[3]{$cur_row_ids{0}}[1]{cds}, __NBC__ );
661
662       (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
663         and
664       push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}}, (
665           $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = [{ title => $cur_row_data->[8] }]
666       );
667       defined($cur_row_data->[8]) or bless ( $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}[1]{tracks}, __NBC__ );
668
669       (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
670         and
671       push @{$collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}}, (
672           $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = [{ cd => $$cur_row_data[2], title => $cur_row_data->[3] }]
673       );
674       defined($cur_row_data->[2]) or bless ( $collapse_idx[0]{$cur_row_ids{10}}[1]{tracks}, __NBC__ );
675     }
676
677     $#{$_[0]} = $result_pos - 1;
678   ',
679   'Multiple has_many on multiple branches with underdefined root torture test',
680 );
681
682 is_same_src (
683   ($schema->source ('CD')->_mk_row_parser({
684     inflate_map => $infmap,
685     collapse => 1,
686     hri_style => 1,
687     prune_null_branches => 1,
688   }))[0],
689   ' my $rows_pos = 0;
690     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids);
691
692     while ($cur_row_data = (
693       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
694         ||
695       ( $_[1] and $rows_pos = -1 and $_[1]->() )
696     ) ) {
697
698       # do not care about nullability here
699       @cur_row_ids{( 0, 2, 3, 4, 8 )} = @{$cur_row_data}[( 0, 2, 3, 4, 8 )];
700
701       # cache expensive set of ops in a non-existent rowid slot
702       $cur_row_ids{10} = (
703         ( ( defined $cur_row_data->[0] ) && (join "\xFF", q{}, $cur_row_ids{0}, q{} ))
704           or
705         ( ( defined $cur_row_data->[2] ) && (join "\xFF", q{}, $cur_row_ids{2}, q{} ))
706           or
707         "\0$rows_pos\0"
708       );
709
710       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
711       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{10}} and (unshift @{$_[2]}, $cur_row_data) and last;
712
713       $collapse_idx[0]{$cur_row_ids{10}} //= $_[0][$result_pos++] = { year => $$cur_row_data[1] };
714
715       (! defined $cur_row_data->[0] ) ? $collapse_idx[0]{$cur_row_ids{10}}{single_track} = undef : do {
716
717         $collapse_idx[0]{$cur_row_ids{10}}{single_track} //= ($collapse_idx[1]{$cur_row_ids{0}} = { trackid => $$cur_row_data[0] });
718
719         $collapse_idx[1]{$cur_row_ids{0}}{cd} //= $collapse_idx[2]{$cur_row_ids{0}};
720
721         $collapse_idx[2]{$cur_row_ids{0}}{artist} //= ($collapse_idx[3]{$cur_row_ids{0}} = { artistid => $$cur_row_data[6] });
722
723         (! defined $cur_row_data->[4] ) ? $collapse_idx[3]{$cur_row_ids{0}}{cds} = [] : do {
724
725           (! $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}} )
726             and
727           push @{$collapse_idx[3]{$cur_row_ids{0}}{cds}}, (
728               $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] }
729           );
730
731           (! defined $cur_row_data->[8] ) ? $collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}{tracks} = [] : do {
732
733             (! $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} )
734               and
735             push @{$collapse_idx[4]{$cur_row_ids{0}}{$cur_row_ids{4}}{tracks}}, (
736                 $collapse_idx[5]{$cur_row_ids{0}}{$cur_row_ids{4}}{$cur_row_ids{8}} = { title => $$cur_row_data[8] }
737             );
738           };
739         };
740       };
741
742       (! defined $cur_row_data->[2] ) ? $collapse_idx[0]{$cur_row_ids{10}}{tracks} = [] : do {
743         (! $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} )
744           and
745         push @{$collapse_idx[0]{$cur_row_ids{10}}{tracks}}, (
746             $collapse_idx[6]{$cur_row_ids{2}}{$cur_row_ids{3}} = { cd => $$cur_row_data[2], title => $$cur_row_data[3] }
747         );
748       };
749     }
750
751     $#{$_[0]} = $result_pos - 1;
752   ',
753   'Multiple has_many on multiple branches with underdefined root, HRI-direct torture test',
754 );
755
756 is_same_src (
757   ($schema->source ('Owners')->_mk_row_parser({
758     inflate_map => [qw( books.title books.owner )],
759     collapse => 1,
760     prune_null_branches => 1,
761   }))[0],
762   ' my $rows_pos = 0;
763     my ($result_pos, @collapse_idx, $cur_row_data, %cur_row_ids );
764
765     while ($cur_row_data = (
766       ( $rows_pos >= 0 and $_[0][$rows_pos++] )
767         or
768       ( $_[1] and $rows_pos = -1 and $_[1]->() )
769     ) ) {
770
771       @cur_row_ids{0,1} = @{$cur_row_data}[0,1];
772
773       $cur_row_ids{3} = (
774         ( ( defined $cur_row_data->[1] ) && (join "\xFF", q{}, $cur_row_ids{1}, q{} ))
775           or
776         "\0${rows_pos}\0"
777       );
778
779       $_[1] and $result_pos and ! $collapse_idx[0]{$cur_row_ids{3}} and (unshift @{$_[2]}, $cur_row_data) and last;
780
781       # empty data for the root node
782       $collapse_idx[0]{$cur_row_ids{3}} //= $_[0][$result_pos++] = [];
783
784       ( ! defined $cur_row_data->[0] ) ? $collapse_idx[0]{$cur_row_ids{3}}[1]{"books"} = [] : do {
785         ( ! $collapse_idx[1]{$cur_row_ids{0}} )
786           and
787         push @{$collapse_idx[0]{$cur_row_ids{3}}[1]{books}},
788           $collapse_idx[1]{$cur_row_ids{0}} = [ { owner => $cur_row_data->[1], title => $cur_row_data->[0] } ]
789       };
790     }
791
792     $#{$_[0]} = $result_pos - 1; # truncate the passed in array to where we filled it with results
793   ',
794   'Non-premultiplied implicit collapse with missing join columns',
795 );
796
797 done_testing;
798
799 my $deparser;
800 sub is_same_src { SKIP: {
801
802   skip "Skipping comparison of unicode-posioned source", 1
803     if DBIx::Class::_ENV_::STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE;
804
805   $deparser ||= B::Deparse->new;
806   local $Test::Builder::Level = $Test::Builder::Level + 1;
807
808   my ($got, $expect) = @_;
809
810   skip "Not testing equality of source containing defined-or operator on this perl $]", 1
811     if ($] < 5.010 and$expect =~ m!\Q//=!);
812
813   $expect =~ s/__NBC__/perlstring($DBIx::Class::ResultSource::RowParser::Util::null_branch_class)/ge;
814
815   $expect = "  { use strict; use warnings FATAL => 'uninitialized';\n$expect\n  }";
816
817   my @normalized = map {
818     my $cref = eval "sub { $_ }" or do {
819       fail "Coderef does not compile!\n\n$@\n\n$_";
820       return undef;
821     };
822     $deparser->coderef2text($cref);
823   } ($got, $expect);
824
825   &is (@normalized, $_[2]||() ) or do {
826     eval { require Test::Differences }
827       ? &Test::Differences::eq_or_diff( @normalized, $_[2]||() )
828       : note ("Original sources:\n\n$got\n\n$expect\n")
829     ;
830     exit 1;
831   };
832 } }