The Top limit emulation bundled with SQLA::Limit assumes that the limited resultset...
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSet.pm
index cf85311..e5ce095 100644 (file)
@@ -661,6 +661,8 @@ sub cursor {
   my ($self) = @_;
 
   my $attrs = $self->_resolved_attrs_copy;
+  $attrs->{_virtual_order_by} = $self->_gen_virtual_order;
+
   return $self->{cursor}
     ||= $self->result_source->storage->select($attrs->{from}, $attrs->{select},
           $attrs->{where},$attrs);
@@ -712,6 +714,8 @@ sub single {
   }
 
   my $attrs = $self->_resolved_attrs_copy;
+  $attrs->{_virtual_order_by} = $self->_gen_virtual_order;
+
   if ($where) {
     if (defined $attrs->{where}) {
       $attrs->{where} = {
@@ -738,6 +742,32 @@ sub single {
   return (@data ? ($self->_construct_object(@data))[0] : undef);
 }
 
+# _gen_virtual_order
+#
+# This is a horrble hack, but seems like the best we can do at this point
+# Some limit emulations (Top) require an ordered resultset in order to 
+# function at all. So supply a PK order if such a condition is detected
+
+sub _gen_virtual_order {
+  my $self = shift;
+  my $attrs = $self->_resolved_attrs_copy;
+
+  if ($attrs->{rows} or $attrs->{offset} ) {
+
+#   This check requires ensure_connected, so probably cheaper to just calculate all the time
+
+#    my $sm = $self->result_source->storage->_sql_maker;
+#
+#    if ($sm->_default_limit_syntax eq 'Top' and not @{$sm->_resolve_order ($attrs->{order_by}) }) {
+
+      return [ $self->result_source->primary_columns ];
+
+#    }
+  }
+
+  return undef;
+}
+
 # _is_unique_query
 #
 # Try to determine if the specified query is guaranteed to be unique, based on
@@ -856,10 +886,10 @@ instead. An example conversion is:
 
 sub search_like {
   my $class = shift;
-  carp join ("\n",
-    'search_like() is deprecated and will be removed in 0.09.',
-    'Instead use ->search({ x => { -like => "y%" } })',
-    '(note the outer pair of {}s - they are important!)'
+  carp (
+    'search_like() is deprecated and will be removed in DBIC version 0.09.'
+   .' Instead use ->search({ x => { -like => "y%" } })'
+   .' (note the outer pair of {}s - they are important!)'
   );
   my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
   my $query = ref $_[0] eq 'HASH' ? { %{shift()} }: {@_};
@@ -1141,32 +1171,46 @@ sub count {
   return $self->search(@_)->count if @_ and defined $_[0];
   return scalar @{ $self->get_cache } if $self->get_cache;
 
-  my @subq_attrs = qw/prefetch collapse distinct group_by having/;
+  my @grouped_subq_attrs = qw/prefetch collapse distinct group_by having/;
+  my @subq_attrs = ();
+  
   my $attrs = $self->_resolved_attrs;
-
   # if we are not paged - we are simply asking for a limit
   if (not $attrs->{page} and not $attrs->{software_limit}) {
     push @subq_attrs, qw/rows offset/;
   }
 
-  return $self->_has_attr (@subq_attrs)
-    ? $self->_count_subq
+  my $need_subq = $self->_has_attr (@subq_attrs);
+  my $need_group_subq = $self->_has_attr (@grouped_subq_attrs);
+
+  return ($need_subq || $need_group_subq)
+    ? $self->_count_subq ($need_group_subq)
     : $self->_count_simple
 }
 
 sub _count_subq {
-  my $self = shift;
+  my ($self, $add_group_by) = @_;
 
   my $attrs = $self->_resolved_attrs_copy;
 
   # copy for the subquery, we need to do some adjustments to it too
   my $sub_attrs = { %$attrs };
 
-  # these can not go in the subquery either
-  delete $sub_attrs->{$_} for qw/prefetch collapse select +select as +as columns +columns/;
+  # these can not go in the subquery, and there is no point of ordering it
+  delete $sub_attrs->{$_} for qw/prefetch collapse select +select as +as columns +columns order_by/;
 
-  # force a group_by and the same set of columns (most databases require this)
-  $sub_attrs->{columns} = $sub_attrs->{group_by} ||= [ map { "$attrs->{alias}.$_" } ($self->result_source->primary_columns) ];
+  # if needed force a group_by and the same set of columns (most databases require this)
+  if ($add_group_by) {
+
+    # if we prefetch, we group_by primary keys only as this is what we would get out of the rs via ->next/->all
+    # simply deleting group_by suffices, as the code below will re-fill it
+    # Note: we check $attrs, as $sub_attrs has collapse deleted
+    if (ref $attrs->{collapse} and keys %{$attrs->{collapse}} ) { 
+      delete $sub_attrs->{group_by};
+    }
+
+    $sub_attrs->{columns} = $sub_attrs->{group_by} ||= [ map { "$attrs->{alias}.$_" } ($self->result_source->primary_columns) ];
+  }
 
   $attrs->{from} = [{
     count_subq => (ref $self)->new ($self->result_source, $sub_attrs )->as_query
@@ -2602,8 +2646,6 @@ sub _resolved_attrs {
 
   }
 
-  $attrs->{group_by} ||= [ grep { !ref($_) || (ref($_) ne 'HASH') } @{$attrs->{select}} ]
-    if delete $attrs->{distinct};
   if ( $attrs->{order_by} ) {
     $attrs->{order_by} = (
       ref( $attrs->{order_by} ) eq 'ARRAY'
@@ -2630,6 +2672,11 @@ sub _resolved_attrs {
     }
     push( @{ $attrs->{order_by} }, @pre_order );
   }
+
+  if (delete $attrs->{distinct}) {
+    $attrs->{group_by} ||= [ grep { !ref($_) || (ref($_) ne 'HASH') } @{$attrs->{select}} ];
+  }
+
   $attrs->{collapse} = $collapse;
 
   if ( $attrs->{page} and not defined $attrs->{offset} ) {