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