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