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