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