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