return $self->search(@_)->count if @_ and defined $_[0];
return scalar @{ $self->get_cache } if $self->get_cache;
- my $meth = $self->_has_resolved_attr (qw/collapse group_by/)
- ? 'count_grouped'
- : 'count'
- ;
-
my $attrs = $self->_resolved_attrs_copy;
+
+ # this is a little optimization - it is faster to do the limit
+ # adjustments in software, instead of a subquery
+ my $rows = delete $attrs->{rows};
+ my $offset = delete $attrs->{offset};
+
+ my $crs;
+ if ($self->_has_resolved_attr (qw/collapse group_by/)) {
+ $crs = $self->_count_subq_rs ($attrs);
+ }
+ else {
+ $crs = $self->_count_rs ($attrs);
+ }
+ my $count = $crs->next;
+
+ $count -= $offset if $offset;
+ $count = $rows if $rows and $rows < $count;
+ $count = 0 if ($count < 0);
+
+ return $count;
+}
+
+=head2 count_rs
+
+=over 4
+
+=item Arguments: $cond, \%attrs??
+
+=item Return Value: $count_rs
+
+=back
+
+Same as L</count> but returns a L<DBIx::Class::ResultSetColumn> object.
+This can be very handy for subqueries:
+
+ ->search( { amount => $some_rs->count_rs->as_query } )
+
+As with regular resultsets the SQL query will be executed only after
+the resultset is accessed via L</next> or L</all>. That would return
+the same single value obtainable via L</count>.
+
+=cut
+
+sub count_rs {
+ my $self = shift;
+ return $self->search(@_)->count_rs if @_;
+
+ # this may look like a lack of abstraction (count() does about the same)
+ # but in fact an _rs *must* use a subquery for the limits, as the
+ # software based limiting can not be ported if this $rs is to be used
+ # in a subquery itself (i.e. ->as_query)
+ if ($self->_has_resolved_attr (qw/collapse group_by offset rows/)) {
+ return $self->_count_subq_rs;
+ }
+ else {
+ return $self->_count_rs;
+ }
+}
+
+#
+# returns a ResultSetColumn object tied to the count query
+#
+sub _count_rs {
+ my ($self, $attrs) = @_;
+
+ my $rsrc = $self->result_source;
+ $attrs ||= $self->_resolved_attrs;
+
+ my $tmp_attrs = { %$attrs };
+
+ # take off any limits, record_filter is cdbi, and no point of ordering a count
+ delete $tmp_attrs->{$_} for (qw/select as rows offset order_by record_filter/);
+
+ # overwrite the selector (supplied by the storage)
+ $tmp_attrs->{select} = $rsrc->storage->_count_select ($rsrc, $tmp_attrs);
+ $tmp_attrs->{as} = 'count';
+
+ my $tmp_rs = $rsrc->resultset_class->new($rsrc, $tmp_attrs)->get_column ('count');
+
+ return $tmp_rs;
+}
+
+#
+# same as above but uses a subquery
+#
+sub _count_subq_rs {
+ my ($self, $attrs) = @_;
+
my $rsrc = $self->result_source;
+ $attrs ||= $self->_resolved_attrs_copy;
- return $rsrc->storage->$meth ($rsrc, $attrs);
+ my $sub_attrs = { %$attrs };
+
+ # these can not go in the subquery, and there is no point of ordering it
+ delete $sub_attrs->{$_} for qw/collapse select as order_by/;
+
+ # if we prefetch, we group_by primary keys only as this is what we would get out of the rs via ->next/->all
+ # clobber old group_by regardless
+ if ( keys %{$attrs->{collapse}} ) {
+ $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } ($rsrc->primary_columns) ]
+ }
+
+ $sub_attrs->{select} = $rsrc->storage->_subq_count_select ($rsrc, $sub_attrs);
+
+ $attrs->{from} = [{
+ count_subq => $rsrc->resultset_class->new ($rsrc, $sub_attrs )->as_query
+ }];
+
+ # the subquery replaces this
+ delete $attrs->{$_} for qw/where bind collapse group_by having having_bind rows offset/;
+
+ return $self->_count_rs ($attrs);
}
+
sub _bool {
return 1;
}
$self->{attrs}{alias} => $source->from,
} ];
- if ( exists $attrs->{join} || exists $attrs->{prefetch} ) {
+ if ( $attrs->{join} || $attrs->{prefetch} ) {
+
+ $self->throw_exception ('join/prefetch can not be used with a literal scalarref {from}')
+ if ref $attrs->{from} ne 'ARRAY';
+
my $join = delete $attrs->{join} || {};
if ( defined $attrs->{prefetch} ) {
$join = $self->_merge_attr( $join, $attrs->{prefetch} );
-
}
$attrs->{from} = # have to copy here to avoid corrupting the original
[
- @{ $attrs->{from} },
- $source->_resolve_join(
- $join, $alias, { %{ $attrs->{seen_join} || {} } }
- )
+ @{ $attrs->{from} },
+ $source->_resolve_join(
+ $join, $alias, { %{ $attrs->{seen_join} || {} } }
+ )
];
-
}
if ( $attrs->{order_by} ) {
return $alias2source;
}
-sub count {
- my ($self, $source, $attrs) = @_;
-
- my $tmp_attrs = { %$attrs };
-
- # take off any limits, record_filter is cdbi, and no point of ordering a count
- delete $tmp_attrs->{$_} for (qw/select as rows offset order_by record_filter/);
-
- # overwrite the selector
- $tmp_attrs->{select} = { count => '*' };
-
- my $tmp_rs = $source->resultset_class->new($source, $tmp_attrs);
- my ($count) = $tmp_rs->cursor->next;
-
- # if the offset/rows attributes are still present, we did not use
- # a subquery, so we need to make the calculations in software
- $count -= $attrs->{offset} if $attrs->{offset};
- $count = $attrs->{rows} if $attrs->{rows} and $attrs->{rows} < $count;
- $count = 0 if ($count < 0);
-
- return $count;
-}
-
-sub count_grouped {
- my ($self, $source, $attrs) = @_;
-
- # copy for the subquery, we need to do some adjustments to it too
- my $sub_attrs = { %$attrs };
-
- # these can not go in the subquery, and there is no point of ordering it
- delete $sub_attrs->{$_} for qw/collapse select as order_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->{group_by} ||= [ map { "$attrs->{alias}.$_" } ($source->primary_columns) ];
- $sub_attrs->{select} = $self->_grouped_count_select ($source, $sub_attrs);
-
- $attrs->{from} = [{
- count_subq => $source->resultset_class->new ($source, $sub_attrs )->as_query
- }];
-
- # the subquery replaces this
- delete $attrs->{$_} for qw/where bind collapse group_by having having_bind rows offset/;
-
- return $self->count ($source, $attrs);
-}
-
+# Returns a counting SELECT for a simple count
+# query. Abstracted so that a storage could override
+# this to { count => 'firstcol' } or whatever makes
+# sense as a performance optimization
+sub _count_select {
+ #my ($self, $source, $rs_attrs) = @_;
+ return { count => '*' };
+}
+
+# Returns a SELECT which will end up in the subselect
+# There may or may not be a group_by, as the subquery
+# might have been called to accomodate a limit
#
-# Returns a SELECT to go with a supplied GROUP BY
-# (caled by count_grouped so a group_by is present)
-# Most databases expect them to match, but some
-# choke in various ways.
+# Most databases would be happy with whatever ends up
+# here, but some choke in various ways.
#
-sub _grouped_count_select {
- my ($self, $source, $rs_args) = @_;
- return $rs_args->{group_by};
+sub _subq_count_select {
+ my ($self, $source, $rs_attrs) = @_;
+ return $rs_attrs->{group_by} if $rs_attrs->{group_by};
+
+ my @pcols = map { join '.', $rs_attrs->{alias}, $_ } ($source->primary_columns);
+ return @pcols ? \@pcols : [ 1 ];
}
+
sub source_bind_attributes {
my ($self, $source) = @_;
-
+
my $bind_attributes;
foreach my $column ($source->columns) {
-
+
my $data_type = $source->column_info($column)->{data_type} || '';
$bind_attributes->{$column} = $self->bind_attribute_by_data_type($data_type)
if $data_type;
# primary keys of the main table in the inner query. This hopefully still
# hits the indexes and keeps mysql happy.
# (mysql does not care if the SELECT and the GROUP BY match)
-sub _grouped_count_select {
- my ($self, $source, $rs_args) = @_;
- my @pcols = map { join '.', $rs_args->{alias}, $_ } ($source->primary_columns);
- return @pcols ? \@pcols : $rs_args->{group_by};
+sub _subq_count_select {
+ my ($self, $source, $rs_attrs) = @_;
+ my @pcols = map { join '.', $rs_attrs->{alias}, $_ } ($source->primary_columns);
+ return @pcols ? \@pcols : [ 1 ];
}
1;