Populate caches for related result sets even if they're empty
Dagfinn Ilmari Mannsåker [Fri, 28 Mar 2014 16:48:17 +0000 (16:48 +0000)]
This avoids unnecessary database hits when accessing prefetched related
resultsets with no rows.

Changes
lib/DBIx/Class/ResultSet.pm
t/prefetch/empty_cache.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 06e9cf0..57e6135 100644 (file)
--- a/Changes
+++ b/Changes
@@ -19,6 +19,8 @@ Revision history for DBIx::Class
         - Fix on_connect_* not always firing in some cases - a race condition
           existed between storage accessor setters and the determine_driver
           routines, triggering a connection before the set-cycle is finished
+        - Prevent erroneous database hit when accessing prefetched related
+          resultsets with no rows
         - Fix incorrect handling of custom relationship conditions returning
           SQLA literal expressions
         - Fix multi-value literal populate not working with simplified bind
index 92665d6..2bc320b 100644 (file)
@@ -3098,11 +3098,11 @@ sub related_resultset {
 
     if (my $cache = $self->get_cache) {
       my @related_cache = map
-        { @{$_->related_resultset($rel)->get_cache||[]} }
+        { $_->related_resultset($rel)->get_cache || () }
         @$cache
       ;
 
-      $new->set_cache(\@related_cache) if @related_cache;
+      $new->set_cache([ map @$_, @related_cache ]) if @related_cache == @$cache;
     }
 
     $new;
diff --git a/t/prefetch/empty_cache.t b/t/prefetch/empty_cache.t
new file mode 100644 (file)
index 0000000..6fa4bb9
--- /dev/null
@@ -0,0 +1,39 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+use lib qw(t/lib);
+use DBICTest;
+
+my $schema = DBICTest->init_schema();
+
+my $no_albums_artist = { name => 'We Have No Albums' };
+$schema->resultset('Artist')->create($no_albums_artist);
+
+foreach (
+  [empty => \'0 = 1', 0],
+  [nonempty => $no_albums_artist, 1],
+) {
+  my ($desc, $cond, $count) = @$_;
+
+  my $artists_rs = $schema->resultset('Artist')
+    ->search($cond, { prefetch => 'cds', cache => 1 });
+
+  $schema->is_executed_querycount( sub {
+    my @artists = $artists_rs->all;
+    is( 0+@{$artists_rs->get_cache}, $count, "$desc cache on original resultset" );
+    is( 0+@artists, $count, "$desc original resultset" );
+  }, 1, "->all on $desc original resultset hit db" );
+
+  $schema->is_executed_querycount( sub {
+    my $cds_rs = $artists_rs->related_resultset('cds');
+    is_deeply( $cds_rs->get_cache, [], 'empty cache on related resultset' );
+
+    my @cds = $cds_rs->all;
+    is( 0+@cds, 0, 'empty related resultset' );
+  }, 0, '->all on empty related resultest didn\'t hit db' );
+}
+
+
+done_testing;