60bf1ba03148894a86f3f422f53733477de97181
[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/single_track.cd.artist.name year/];
19
20 is_same_src (
21   $schema->source ('CD')->_mk_row_parser({
22     inflate_map => $infmap,
23   }),
24   '$_ = [
25     { year => $_->[1] },
26     { single_track => [
27       undef,
28       { cd => [
29         undef,
30         { artist => [
31           { name  => $_->[0] },
32         ] },
33       ]},
34     ]},
35   ] for @{$_[0]}',
36   'Simple 1:1 descending non-collapsing parser',
37 );
38
39 $infmap = [qw/
40   single_track.cd.artist.cds.tracks.title
41   single_track.cd.artist.artistid
42   year
43   single_track.cd.artist.cds.cdid
44   title
45   artist
46 /];
47 is_same_src (
48   $schema->source ('CD')->_mk_row_parser({
49     inflate_map => $infmap,
50   }),
51   '$_ = [
52     { artist => $_->[5], title => $_->[4], year => $_->[2] },
53     { single_track => [
54       undef,
55       { cd => [
56         undef,
57         { artist => [
58           { artistid => $_->[1] },
59           { cds => [
60             { cdid => $_->[3] },
61             { tracks => [
62               { title => $_->[0] }
63             ] },
64           ] },
65         ] },
66       ] },
67     ] },
68   ] for @{$_[0]}',
69   '1:1 descending non-collapsing parser terminating with chained 1:M:M',
70 );
71
72 is_deeply (
73   ($schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} })),
74   {
75     -node_index => 1,
76     -idcols_current_node => [ 4, 5 ],
77     -idcols_extra_from_children => [ 0, 3 ],
78
79     single_track => {
80       -node_index => 2,
81       -idcols_current_node => [ 4, 5 ],
82       -idcols_extra_from_children => [ 0, 3 ],
83       -is_optional => 1,
84       -is_single => 1,
85
86       cd => {
87         -node_index => 3,
88         -idcols_current_node => [ 4, 5 ],
89         -idcols_extra_from_children => [ 0, 3 ],
90         -is_single => 1,
91
92         artist => {
93           -node_index => 4,
94           -idcols_current_node => [ 4, 5 ],
95           -idcols_extra_from_children => [ 0, 3 ],
96           -is_single => 1,
97
98           cds => {
99             -node_index => 5,
100             -idcols_current_node => [ 3, 4, 5 ],
101             -idcols_extra_from_children => [ 0 ],
102             -is_optional => 1,
103
104             tracks => {
105               -node_index => 6,
106               -idcols_current_node => [ 0, 3, 4, 5 ],
107               -is_optional => 1,
108             },
109           },
110         },
111       },
112     },
113   },
114   'Correct collapse map for 1:1 descending chain terminating with chained 1:M:M'
115 );
116
117 is_same_src (
118   $schema->source ('CD')->_mk_row_parser({
119     inflate_map => $infmap,
120     collapse => 1,
121   }),
122   ' my($rows_pos, $result_pos, $cur_row, @cur_row_ids, @collapse_idx, $is_new_res) = (0, 0);
123
124     while ($cur_row = (
125       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
126         ||
127       ( $_[1] and $_[1]->() )
128     ) {
129
130       $cur_row_ids[$_] = defined $cur_row->[$_] ? $cur_row->[$_] : "\xFF\xFFN\xFFU\xFFL\xFFL\xFF\xFF"
131         for (0, 3, 4, 5);
132
133       # a present cref in $_[1] implies lazy prefetch, implies a supplied stash in $_[2]
134       $_[1] and $result_pos and unshift(@{$_[2]}, $cur_row) and last
135         if $is_new_res = ! $collapse_idx[1]{$cur_row_ids[4]}{$cur_row_ids[5]};
136
137       # the rowdata itself for root node
138       $collapse_idx[1]{$cur_row_ids[4]}{$cur_row_ids[5]} ||= [{ artist => $cur_row->[5], title => $cur_row->[4], year => $cur_row->[2] }];
139
140       # prefetch data of single_track (placed in root)
141       $collapse_idx[1]{$cur_row_ids[4]}{$cur_row_ids[5]}[1]{single_track} ||= $collapse_idx[2]{$cur_row_ids[4]}{$cur_row_ids[5]};
142
143       # prefetch data of cd (placed in single_track)
144       $collapse_idx[2]{$cur_row_ids[4]}{$cur_row_ids[5]}[1]{cd} ||= $collapse_idx[3]{$cur_row_ids[4]}{$cur_row_ids[5]};
145
146       # prefetch data of artist ( placed in single_track->cd)
147       $collapse_idx[3]{$cur_row_ids[4]}{$cur_row_ids[5]}[1]{artist} ||= $collapse_idx[4]{$cur_row_ids[4]}{$cur_row_ids[5]} ||= [{ artistid => $cur_row->[1] }];
148
149       # prefetch data of cds (if available)
150       push @{$collapse_idx[4]{$cur_row_ids[4]}{$cur_row_ids[5]}[1]{cds}}, $collapse_idx[5]{$cur_row_ids[3]}{$cur_row_ids[4]}{$cur_row_ids[5]} ||= [{ cdid => $cur_row->[3] }]
151         unless $collapse_idx[5]{$cur_row_ids[3]}{$cur_row_ids[4]}{$cur_row_ids[5]};
152
153       # prefetch data of tracks (if available)
154       push @{$collapse_idx[5]{$cur_row_ids[3]}{$cur_row_ids[4]}{$cur_row_ids[5]}[1]{tracks}}, $collapse_idx[6]{$cur_row_ids[0]}{$cur_row_ids[3]}{$cur_row_ids[4]}{$cur_row_ids[5]} ||= [{ title => $cur_row->[0] }]
155         unless $collapse_idx[6]{$cur_row_ids[0]}{$cur_row_ids[3]}{$cur_row_ids[4]}{$cur_row_ids[5]};
156
157       $_[0][$result_pos++] = $collapse_idx[1]{$cur_row_ids[4]}{$cur_row_ids[5]}
158         if $is_new_res;
159     }
160     splice @{$_[0]}, $result_pos;
161   ',
162   'Same 1:1 descending terminating with chained 1:M:M but with collapse',
163 );
164
165 $infmap = [qw/
166   tracks.lyrics.lyric_versions.text
167   existing_single_track.cd.artist.artistid
168   existing_single_track.cd.artist.cds.year
169   year
170   genreid
171   tracks.title
172   existing_single_track.cd.artist.cds.cdid
173   latest_cd
174   existing_single_track.cd.artist.cds.tracks.title
175   existing_single_track.cd.artist.cds.genreid
176 /];
177
178 is_deeply (
179   $schema->source('CD')->_resolve_collapse({ as => {map { $infmap->[$_] => $_ } 0 .. $#$infmap} }),
180   {
181     -node_index => 1,
182     -idcols_current_node => [ 1 ], # existing_single_track.cd.artist.artistid
183     -idcols_extra_from_children => [ 0, 5, 6, 8 ],
184
185     existing_single_track => {
186       -node_index => 2,
187       -idcols_current_node => [ 1 ], # existing_single_track.cd.artist.artistid
188       -idcols_extra_from_children => [ 6, 8 ],
189       -is_single => 1,
190
191       cd => {
192         -node_index => 3,
193         -idcols_current_node => [ 1 ], # existing_single_track.cd.artist.artistid
194         -idcols_extra_from_children => [ 6, 8 ],
195         -is_single => 1,
196
197         artist => {
198           -node_index => 4,
199           -idcols_current_node => [ 1 ], # existing_single_track.cd.artist.artistid
200           -idcols_extra_from_children => [ 6, 8 ],
201           -is_single => 1,
202
203           cds => {
204             -node_index => 5,
205             -idcols_current_node => [ 1, 6 ], # existing_single_track.cd.artist.cds.cdid
206             -idcols_extra_from_children => [ 8 ],
207             -is_optional => 1,
208
209             tracks => {
210               -node_index => 6,
211               -idcols_current_node => [ 1, 6, 8 ], # existing_single_track.cd.artist.cds.cdid, existing_single_track.cd.artist.cds.tracks.title
212               -is_optional => 1,
213             }
214           }
215         }
216       }
217     },
218     tracks => {
219       -node_index => 7,
220       -idcols_current_node => [ 1, 5 ], # existing_single_track.cd.artist.artistid, tracks.title
221       -idcols_extra_from_children => [ 0 ],
222       -is_optional => 1,
223
224       lyrics => {
225         -node_index => 8,
226         -idcols_current_node => [ 1, 5 ], # existing_single_track.cd.artist.artistid, tracks.title
227         -idcols_extra_from_children => [ 0 ],
228         -is_single => 1,
229         -is_optional => 1,
230
231         lyric_versions => {
232           -node_index => 9,
233           -idcols_current_node => [ 0, 1, 5 ], # tracks.lyrics.lyric_versions.text, existing_single_track.cd.artist.artistid, tracks.title
234           -is_optional => 1,
235         },
236       },
237     }
238   },
239   'Correct collapse map constructed',
240 );
241
242 is_same_src (
243   $schema->source ('CD')->_mk_row_parser({
244     inflate_map => $infmap,
245     collapse => 1,
246   }),
247   ' my ($rows_pos, $result_pos, $cur_row, @cur_row_ids, @collapse_idx, $is_new_res) = (0,0);
248
249     while ($cur_row = (
250       ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } )
251         ||
252       ( $_[1] and $_[1]->() )
253     ) {
254
255       $cur_row_ids[$_] = defined $cur_row->[$_] ? $cur_row->[$_] : "\xFF\xFFN\xFFU\xFFL\xFFL\xFF\xFF"
256         for (0, 1, 5, 6, 8);
257
258       $is_new_res = ! $collapse_idx[1]{$cur_row_ids[1]} and (
259         $_[1] and $result_pos and (unshift @{$_[2]}, $cur_row) and last
260       );
261
262       $collapse_idx[1]{$cur_row_ids[1]} ||= [{ genreid => $cur_row->[4], latest_cd => $cur_row->[7], year => $cur_row->[3] }];
263
264       $collapse_idx[1]{$cur_row_ids[1]}[1]{existing_single_track} ||= $collapse_idx[2]{$cur_row_ids[1]};
265       $collapse_idx[2]{$cur_row_ids[1]}[1]{cd} ||= $collapse_idx[3]{$cur_row_ids[1]};
266       $collapse_idx[3]{$cur_row_ids[1]}[1]{artist} ||= $collapse_idx[4]{$cur_row_ids[1]} ||= [{ artistid => $cur_row->[1] }];
267
268       push @{ $collapse_idx[4]{$cur_row_ids[1]}[1]{cds} }, $collapse_idx[5]{$cur_row_ids[1]}{$cur_row_ids[6]} ||= [{ cdid => $cur_row->[6], genreid => $cur_row->[9], year => $cur_row->[2] }]
269         unless $collapse_idx[5]{$cur_row_ids[1]}{$cur_row_ids[6]};
270
271       push @{ $collapse_idx[5]{$cur_row_ids[1]}{$cur_row_ids[6]}[1]{tracks} }, $collapse_idx[6]{$cur_row_ids[1]}{$cur_row_ids[6]}{$cur_row_ids[8]} ||= [{ title => $cur_row->[8] }]
272         unless $collapse_idx[6]{$cur_row_ids[1]}{$cur_row_ids[6]}{$cur_row_ids[8]};
273
274       push @{ $collapse_idx[1]{$cur_row_ids[1]}[1]{tracks} }, $collapse_idx[7]{$cur_row_ids[1]}{$cur_row_ids[5]} ||= [{ title => $cur_row->[5] }]
275         unless $collapse_idx[7]{$cur_row_ids[1]}{$cur_row_ids[5]};
276
277       $collapse_idx[7]{$cur_row_ids[1]}{$cur_row_ids[5]}[1]{lyrics} ||= $collapse_idx[8]{$cur_row_ids[1]}{$cur_row_ids[5] };
278
279       push @{ $collapse_idx[8]{$cur_row_ids[1]}{$cur_row_ids[5]}[1]{lyric_versions} }, $collapse_idx[9]{$cur_row_ids[0]}{$cur_row_ids[1]}{$cur_row_ids[5]} ||= [{ text => $cur_row->[0] }]
280         unless $collapse_idx[9]{$cur_row_ids[0]}{$cur_row_ids[1]}{$cur_row_ids[5]};
281
282       $_[0][$result_pos++] = $collapse_idx[1]{$cur_row_ids[1]}
283         if $is_new_res;
284     }
285
286     splice @{$_[0]}, $result_pos;
287   ',
288   'Multiple has_many on multiple branches torture test',
289 );
290
291 done_testing;
292
293 my $deparser;
294 sub is_same_src {
295   $deparser ||= B::Deparse->new;
296   local $Test::Builder::Level = $Test::Builder::Level + 1;
297
298   my ($got, $expect) = map {
299     my $cref = eval "sub { $_ }" or do {
300       fail "Coderef does not compile!\n\n$@\n\n$_";
301       return undef;
302     };
303     $deparser->coderef2text($cref);
304   } @_[0,1];
305
306   is ($got, $expect, $_[2]||() )
307     or note ("Originals source:\n\n$_[0]\n\n$_[1]\n");
308 }
309