This is tested elsewhere (2)
[dbsrgits/DBIx-Class.git] / t / prefetch / multiple_hasmany.t
CommitLineData
e9bd1473 1use strict;
2use warnings;
3
4use Test::More;
5use Test::Exception;
6use lib qw(t/lib);
7use DBICTest;
8use Data::Dumper;
9
2e251255 10plan tests => 10;
e9bd1473 11
2e251255 12my $schema = DBICTest->init_schema();
e9bd1473 13
14use IO::File;
15
e9bd1473 16# once the following TODO is complete, remove the 2 warning tests immediately
17# after the TODO block
18# (the TODO block itself contains tests ensuring that the warns are removed)
19TODO: {
20 local $TODO = 'Prefetch of multiple has_many rels at the same level (currently warn to protect the clueless git)';
21
22 #( 1 -> M + M )
23 my $cd_rs = $schema->resultset('CD')->search ({ 'me.title' => 'Forkful of bees' });
24 my $pr_cd_rs = $cd_rs->search ({}, {
25 prefetch => [qw/tracks tags/],
26 });
27
28 my $tracks_rs = $cd_rs->first->tracks;
29 my $tracks_count = $tracks_rs->count;
30
31 my ($pr_tracks_rs, $pr_tracks_count);
32
33 my $queries = 0;
34 $schema->storage->debugcb(sub { $queries++ });
35 $schema->storage->debug(1);
36
37 my $o_mm_warn;
38 {
39 local $SIG{__WARN__} = sub { $o_mm_warn = shift };
40 $pr_tracks_rs = $pr_cd_rs->first->tracks;
41 };
42 $pr_tracks_count = $pr_tracks_rs->count;
43
44 ok(! $o_mm_warn, 'no warning on attempt to prefetch several same level has_many\'s (1 -> M + M)');
45
46 is($queries, 1, 'prefetch one->(has_many,has_many) ran exactly 1 query');
47 is($pr_tracks_count, $tracks_count, 'equal count of prefetched relations over several same level has_many\'s (1 -> M + M)');
48
49 for ($pr_tracks_rs, $tracks_rs) {
50 $_->result_class ('DBIx::Class::ResultClass::HashRefInflator');
51 }
52
53 is_deeply ([$pr_tracks_rs->all], [$tracks_rs->all], 'same structure returned with and without prefetch over several same level has_many\'s (1 -> M + M)');
54
55 #( M -> 1 -> M + M )
56 my $note_rs = $schema->resultset('LinerNotes')->search ({ notes => 'Buy Whiskey!' });
57 my $pr_note_rs = $note_rs->search ({}, {
58 prefetch => {
59 cd => [qw/tags tracks/]
60 },
61 });
62
63 my $tags_rs = $note_rs->first->cd->tags;
64 my $tags_count = $tags_rs->count;
65
66 my ($pr_tags_rs, $pr_tags_count);
67
68 $queries = 0;
69 $schema->storage->debugcb(sub { $queries++ });
70 $schema->storage->debug(1);
71
72 my $m_o_mm_warn;
73 {
74 local $SIG{__WARN__} = sub { $m_o_mm_warn = shift };
75 $pr_tags_rs = $pr_note_rs->first->cd->tags;
76 };
77 $pr_tags_count = $pr_tags_rs->count;
78
79 ok(! $m_o_mm_warn, 'no warning on attempt to prefetch several same level has_many\'s (M -> 1 -> M + M)');
80
81 is($queries, 1, 'prefetch one->(has_many,has_many) ran exactly 1 query');
82
83 is($pr_tags_count, $tags_count, 'equal count of prefetched relations over several same level has_many\'s (M -> 1 -> M + M)');
84
85 for ($pr_tags_rs, $tags_rs) {
86 $_->result_class ('DBIx::Class::ResultClass::HashRefInflator');
87 }
88
89 is_deeply ([$pr_tags_rs->all], [$tags_rs->all], 'same structure returned with and without prefetch over several same level has_many\'s (M -> 1 -> M + M)');
90}
91
92# remove this closure once the TODO above is working
e9bd1473 93{
2e251255 94 my $warn_re = qr/will explode the number of row objects retrievable via/;
95
96 my (@w, @dummy);
97 local $SIG{__WARN__} = sub { $_[0] =~ $warn_re ? push @w, @_ : warn @_ };
e9bd1473 98
99 my $rs = $schema->resultset('CD')->search ({ 'me.title' => 'Forkful of bees' }, { prefetch => [qw/tracks tags/] });
2e251255 100 @w = ();
101 @dummy = $rs->first;
102 is (@w, 1, 'warning on attempt prefetching several same level has_manys (1 -> M + M)');
103
e9bd1473 104 my $rs2 = $schema->resultset('LinerNotes')->search ({ notes => 'Buy Whiskey!' }, { prefetch => { cd => [qw/tags tracks/] } });
2e251255 105 @w = ();
106 @dummy = $rs2->first;
107 is (@w, 1, 'warning on attempt prefetching several same level has_manys (M -> 1 -> M + M)');
e9bd1473 108}
87333f6c 109
110__END__
111The solution is to rewrite ResultSet->_collapse_result() and
112ResultSource->resolve_prefetch() to focus on the final results from the collapse
113of the data. Right now, the code doesn't treat the columns from the various
114tables as grouped entities. While there is a concept of hierarchy (so that
115prefetching down relationships does work as expected), there is no idea of what
116the final product should look like and how the various columns in the row would
117play together. So, the actual prefetch datastructure from the search would be
118very useful in working through this problem. We already have access to the PKs
119and sundry for those. So, when collapsing the search result, we know we are
120looking for 1 cd object. We also know we're looking for tracks and tags records
121-independently- of each other. So, we can grab the data for tracks and data for
122tags separately, uniqueing on the PK as appropriate. Then, when we're done with
123the given cd object's datastream, we know we're good. This should work for all
124the various scenarios.
125
126My reccommendation is the row's data is preprocessed first, breaking it up into
127the data for each of the component tables. (This could be done in the single
128table case, too, but probably isn't necessary.) So, starting with something
129like:
130 my $row = {
131 t1.col1 => 1,
132 t1.col2 => 2,
133 t2.col1 => 3,
134 t2.col2 => 4,
135 t3.col1 => 5,
136 t3.col2 => 6,
137 };
138it is massaged to look something like:
139 my $row_massaged = {
140 t1 => { col1 => 1, col2 => 2 },
141 t2 => { col1 => 3, col2 => 4 },
142 t3 => { col1 => 5, col2 => 6 },
143 };
144At this point, find the stuff that's different is easy enough to do and slotting
fec7084a 145things into the right spot is, likewise, pretty straightforward. Instead of
146storing things in a AoH, store them in a HoH keyed on the PKs of the the table,
147then convert to an AoH after all collapsing is done.
87333f6c 148
149This implies that the collapse attribute can probably disappear or, at the
150least, be turned into a boolean (which is how it's used in every other place).