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