More tests and a really working count_rs
Peter Rabbitson [Sat, 20 Jun 2009 21:30:10 +0000 (21:30 +0000)]
Changes
lib/DBIx/Class/Storage/DBI.pm
t/count/count_rs.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 0ff9a38..3bd0d4f 100644 (file)
--- a/Changes
+++ b/Changes
@@ -5,6 +5,8 @@ Revision history for DBIx::Class
         - Fixed HRI returning too many empty results on multilevel
           nonexisting prefetch
         - Fixed the prefetch with limit bug
+        - New resultsed method count_rs, returns a ::ResultSetColumn
+          returning a single count value
 
 0.08107 2009-06-14 08:21:00 (UTC)
         - Fix serialization regression introduced in 0.08103 (affects
index 707b305..7998423 100644 (file)
@@ -1296,6 +1296,7 @@ sub _adjust_select_args_for_limited_prefetch {
   $self->throw_exception ('Prefetch with limit (rows/offset) is not supported on resultsets with a custom from attribute')
     if (ref $from ne 'ARRAY');
 
+
   # separate attributes
   my $sub_attrs = { %$attrs };
   delete $attrs->{$_} for qw/where bind rows offset/;
@@ -1314,13 +1315,31 @@ sub _adjust_select_args_for_limited_prefetch {
     ];
   }
 
+  # mangle {from}
+  $from = [ @$from ];
+  my $select_root = shift @$from;
+  my @outer_from = @$from;
 
-  # mangle the head of the {from}
-  my $self_ident = shift @$from;
-
+  my %inner_joins;
   my %join_info = map { $_->[0]{-alias} => $_->[0] } (@$from);
 
-  my (%inner_joins);
+  # in complex search_related chains $alias may *not* be 'me'
+  # so always include it in the inner join, and also shift away
+  # from the outer stack, so that the two datasets actually do
+  # meet
+  if ($select_root->{-alias} ne $alias) {
+    $inner_joins{$alias} = 1;
+
+    while (@outer_from && $outer_from[0][0]{-alias} ne $alias) {
+      shift @outer_from;
+    }
+    if (! @outer_from) {
+      $self->throw_exception ("Unable to find '$alias' in the {from} stack, something is wrong");
+    }
+
+    shift @outer_from; # the new subquery will represent this alias, so get rid of it
+  }
+
 
   # decide which parts of the join will remain on the inside
   #
@@ -1379,22 +1398,21 @@ sub _adjust_select_args_for_limited_prefetch {
   }
 
   # construct the inner $from for the subquery
-  my $inner_from = [ $self_ident ];
-  if (keys %inner_joins) {
-    for my $j (@$from) {
-      push @$inner_from, $j if $inner_joins{$j->[0]{-alias}};
-    }
+  my $inner_from = [ $select_root ];
+  for my $j (@$from) {
+    push @$inner_from, $j if $inner_joins{$j->[0]{-alias}};
+  }
 
-    # if a multi-type join was needed in the subquery ("multi" is indicated by
-    # presence in {collapse}) - add a group_by to simulate the collapse in the subq
-    for my $alias (keys %inner_joins) {
+  # if a multi-type join was needed in the subquery ("multi" is indicated by
+  # presence in {collapse}) - add a group_by to simulate the collapse in the subq
 
-      # the dot comes from some weirdness in collapse
-      # remove after the rewrite
-      if ($attrs->{collapse}{".$alias"}) {
-        $sub_attrs->{group_by} = $sub_select;
-        last;
-      }
+  for my $alias (keys %inner_joins) {
+
+    # the dot comes from some weirdness in collapse
+    # remove after the rewrite
+    if ($attrs->{collapse}{".$alias"}) {
+      $sub_attrs->{group_by} = $sub_select;
+      last;
     }
   }
 
@@ -1406,14 +1424,14 @@ sub _adjust_select_args_for_limited_prefetch {
     $sub_attrs
   );
 
-  # put it back in $from
-  unshift @$from, { $alias => $subq };
+  # put it in the new {from}
+  unshift @outer_from, { $alias => $subq };
 
   # This is totally horrific - the $where ends up in both the inner and outer query
   # Unfortunately not much can be done until SQLA2 introspection arrives
   #
   # OTOH it can be seen as a plus: <ash> (notes that this query would make a DBA cry ;)
-  return ($from, $select, $where, $attrs);
+  return (\@outer_from, $select, $where, $attrs);
 }
 
 sub _resolve_ident_sources {
diff --git a/t/count/count_rs.t b/t/count/count_rs.t
new file mode 100644 (file)
index 0000000..7153d3e
--- /dev/null
@@ -0,0 +1,119 @@
+use strict;
+use warnings;
+
+use lib qw(t/lib);
+
+use Test::More;
+use DBICTest;
+use DBIC::SqlMakerTest;
+use DBIC::DebugObj;
+
+plan tests => 10;
+
+my $schema = DBICTest->init_schema();
+
+# non-collapsing prefetch (no multi prefetches)
+{
+  my $rs = $schema->resultset("CD")
+            ->search_related('tracks',
+                { position => [1,2] },
+                { prefetch => [qw/disc lyrics/], rows => 3, offset => 8 },
+            );
+  is ($rs->all, 2, 'Correct number of objects');
+
+
+  my ($sql, @bind);
+  $schema->storage->debugobj(DBIC::DebugObj->new(\$sql, \@bind));
+  $schema->storage->debug(1);
+
+  is ($rs->count, 2, 'Correct count via count()');
+
+  is_same_sql_bind (
+    $sql,
+    \@bind,
+    'SELECT COUNT( * )
+      FROM cd me
+      LEFT JOIN track tracks ON tracks.cd = me.cdid
+      JOIN cd disc ON disc.cdid = tracks.cd
+      LEFT JOIN lyrics lyrics ON lyrics.track_id = tracks.trackid 
+     WHERE ( ( position = ? OR position = ? ) )
+    ',
+    [ qw/'1' '2'/ ],
+    'count softlimit applied',
+  );
+
+  my $crs = $rs->count_rs;
+  is ($crs->next, 2, 'Correct count via count_rs()');
+
+  is_same_sql_bind (
+    $crs->as_query,
+    '(SELECT COUNT( * )
+       FROM (
+        SELECT tracks.trackid
+          FROM cd me
+          LEFT JOIN track tracks ON tracks.cd = me.cdid
+          JOIN cd disc ON disc.cdid = tracks.cd
+          LEFT JOIN lyrics lyrics ON lyrics.track_id = tracks.trackid 
+        WHERE ( ( position = ? OR position = ? ) )
+        LIMIT 3 OFFSET 8
+       ) count_subq
+    )',
+    [ [ position => 1 ], [ position => 2 ] ],
+    'count_rs db-side limit applied',
+  );
+}
+
+# has_many prefetch with limit
+{
+  my $rs = $schema->resultset("Artist")
+            ->search_related('cds',
+                { 'tracks.position' => [1,2] },
+                { prefetch => [qw/tracks artist/], rows => 3, offset => 4 },
+            );
+  is ($rs->all, 1, 'Correct number of objects');
+
+  my ($sql, @bind);
+  $schema->storage->debugobj(DBIC::DebugObj->new(\$sql, \@bind));
+  $schema->storage->debug(1);
+
+  is ($rs->count, 1, 'Correct count via count()');
+
+  is_same_sql_bind (
+    $sql,
+    \@bind,
+    'SELECT COUNT( * )
+      FROM (
+        SELECT cds.cdid
+          FROM artist me
+          LEFT JOIN cd cds ON cds.artist = me.artistid
+          LEFT JOIN track tracks ON tracks.cd = cds.cdid
+          JOIN artist artist ON artist.artistid = cds.artist
+        WHERE tracks.position = ? OR tracks.position = ?
+        GROUP BY cds.cdid
+      ) count_subq
+    ',
+    [ qw/'1' '2'/ ],
+    'count softlimit applied',
+  );
+
+  my $crs = $rs->count_rs;
+  is ($crs->next, 1, 'Correct count via count_rs()');
+
+  is_same_sql_bind (
+    $crs->as_query,
+    '(SELECT COUNT( * )
+      FROM (
+        SELECT cds.cdid
+          FROM artist me
+          LEFT JOIN cd cds ON cds.artist = me.artistid
+          LEFT JOIN track tracks ON tracks.cd = cds.cdid
+          JOIN artist artist ON artist.artistid = cds.artist
+        WHERE tracks.position = ? OR tracks.position = ?
+        GROUP BY cds.cdid
+        LIMIT 3 OFFSET 4
+      ) count_subq
+    )',
+    [ [ 'tracks.position' => 1 ], [ 'tracks.position' => 2 ] ],
+    'count_rs db-side limit applied',
+  );
+}