Query returned more than one row
-In this case, you should be using L</first> or L</find> instead, or if you really
+In this case, you should be using L</next> or L</find> instead, or if you really
know what you are doing, use the L</rows> attribute to explicitly limit the size
of the resultset.
+This method will also throw an exception if it is called on a resultset prefetching
+has_many, as such a prefetch implies fetching multiple rows from the database in
+order to assemble the resulting object.
+
=back
=cut
my $attrs = $self->_resolved_attrs_copy;
+ if (keys %{$attrs->{collapse}}) {
+ $self->throw_exception(
+ 'single() can not be used on resultsets prefetching has_many. Use find( \%cond ) or next() instead'
+ );
+ }
+
if ($where) {
if (defined $attrs->{where}) {
$attrs->{where} = {
return $self->search(@_)->count if @_ and defined $_[0];
return scalar @{ $self->get_cache } if $self->get_cache;
- my $meth = $self->_has_attr (qw/prefetch collapse distinct group_by/)
+ my $meth = $self->_has_resolved_attr (qw/collapse group_by/)
? 'count_grouped'
: 'count'
;
my $rsrc = $self->result_source;
- my $needs_group_by_subq = $self->_has_attr (qw/prefetch distinct join seen_join group_by/);
- my $needs_subq = $self->_has_attr (qw/row offset page/);
+ my $needs_group_by_subq = $self->_has_resolved_attr (qw/collapse group_by -join/);
+ my $needs_subq = $self->_has_resolved_attr (qw/row offset/);
if ($needs_group_by_subq or $needs_subq) {
# make a new $rs selecting only the PKs (that's all we really need)
my $attrs = $self->_resolved_attrs_copy;
- delete $attrs->{$_} for qw/prefetch collapse select +select as +as columns +columns/;
+ delete $attrs->{$_} for qw/collapse select as/;
$attrs->{columns} = [ map { "$attrs->{alias}.$_" } ($self->result_source->primary_columns) ];
if ($needs_group_by_subq) {
return 0;
}
-# _has_attr
+# _has_resolved_attr
#
# determines if the resultset defines at least one
# of the attributes supplied
#
# used to determine if a subquery is neccessary
-sub _has_attr {
+sub _has_resolved_attr {
my ($self, @attr_names) = @_;
my $attrs = $self->_resolved_attrs;
my $join_check_req;
for my $n (@attr_names) {
- ++$join_check_req if $n =~ /join/;
+ ++$join_check_req if $n eq '-join';
my $attr = $attrs->{$n};
}
}
- # a join can be expressed as a multi-level from
+ # a resolved join is expressed as a multi-level from
return 1 if (
$join_check_req
and
sub as_query {
my $self = shift;
- return $self->result_source->storage->as_query($self);
+
+ my $attrs = $self->_resolved_attrs_copy;
+
+ # For future use:
+ #
+ # in list ctx:
+ # my ($sql, \@bind, \%dbi_bind_attrs) = _select_args_to_query (...)
+ # $sql also has no wrapping parenthesis in list ctx
+ #
+ my $sqlbind = $self->result_source->storage
+ ->_select_args_to_query ($attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs);
+
+ return $sqlbind;
}
=head2 find_or_new
my $source = $self->result_source;
my $attrs = $self->{attrs};
- my $from = $attrs->{from}
- || [ {
- -result_source => $source,
- -alias => $attrs->{alias},
- $attrs->{alias} => $source->from,
- } ];
+ my $from = [ @{
+ $attrs->{from}
+ ||
+ [{
+ -source_handle => $source->handle,
+ -alias => $attrs->{alias},
+ $attrs->{alias} => $source->from,
+ }]
+ }];
my $seen = { %{$attrs->{seen_join} || {} } };
}
$attrs->{from} ||= [ {
- -result_source => $source,
+ -source_handle => $source->handle,
-alias => $self->{attrs}{alias},
$self->{attrs}{alias} => $source->from,
} ];
# Although this is needed only if the order_by is not defined, it is
# actually cheaper to just populate this rather than properly examining
# order_by (stuf like [ {} ] and the like)
- $attrs->{_virtual_order_by} = [ $self->result_source->primary_columns ];
+ my $prefix = $alias . ($source->schema->storage->_sql_maker_opts->{name_sep} || '.');
+ $attrs->{_virtual_order_by} = [
+ map { $prefix . $_ } ($source->primary_columns)
+ ];
-
- my $collapse = $attrs->{collapse} || {};
+ $attrs->{collapse} ||= {};
if ( my $prefetch = delete $attrs->{prefetch} ) {
$prefetch = $self->_merge_attr( {}, $prefetch );
- my @pre_order;
- foreach my $p ( ref $prefetch eq 'ARRAY' ? @$prefetch : ($prefetch) ) {
-
- # bring joins back to level of current class
- my $join_map = $self->_joinpath_aliases ($attrs->{from}, $attrs->{seen_join});
- my @prefetch =
- $source->_resolve_prefetch( $p, $alias, $join_map, \@pre_order, $collapse );
- push( @{ $attrs->{select} }, map { $_->[0] } @prefetch );
- push( @{ $attrs->{as} }, map { $_->[1] } @prefetch );
- }
- push( @{ $attrs->{order_by} }, @pre_order );
+
+ my $prefetch_ordering = [];
+
+ my $join_map = $self->_joinpath_aliases ($attrs->{from}, $attrs->{seen_join});
+
+ my @prefetch =
+ $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} );
+
+ push( @{ $attrs->{select} }, map { $_->[0] } @prefetch );
+ push( @{ $attrs->{as} }, map { $_->[1] } @prefetch );
+
+ push( @{ $attrs->{order_by} }, @$prefetch_ordering );
+ $attrs->{_collapse_order_by} = \@$prefetch_ordering;
}
+
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} ) {
- $attrs->{offset} = ( $attrs->{rows} * ( $attrs->{page} - 1 ) );
+ # if both page and offset are specified, produce a combined offset
+ # even though it doesn't make much sense, this is what pre 081xx has
+ # been doing
+ if (my $page = delete $attrs->{page}) {
+ $attrs->{offset} = ($attrs->{rows} * ($page - 1)) +
+ ($attrs->{offset} || 0);
}
return $self->{_attrs} = $attrs;
# SELECT child.* FROM person child
# INNER JOIN person father ON child.father_id = father.id
-If you need to express really complex joins or you need a subselect, you
+You can select from a subquery by passing a resultset to from as follows.
+
+ $schema->resultset('Artist')->search(
+ undef,
+ { alias => 'artist2',
+ from => [ { artist2 => $artist_rs->as_query } ],
+ } );
+
+ # and you'll get sql like this..
+ # SELECT artist2.artistid, artist2.name, artist2.rank, artist2.charfield FROM
+ # ( SELECT me.artistid, me.name, me.rank, me.charfield FROM artists me ) artist2
+
+If you need to express really complex joins, you
can supply literal SQL to C<from> via a scalar reference. In this case
-the contents of the scalar will replace the table name asscoiated with the
+the contents of the scalar will replace the table name associated with the
resultsource.
WARNING: This technique might very well not work as expected on chained