X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=2160cf02a22cf4bb2c0eb61725ec4e3a89804f5a;hb=51a296b402cacffe9d7ef3e6c9f890986b3b6c45;hp=bdd124aea491eb94c526ad365bd50e873541d660;hpb=1685b25e531b0446832933e0f498ddff8855b285;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm index bdd124a..2160cf0 100644 --- a/lib/DBIx/Class/ResultSet.pm +++ b/lib/DBIx/Class/ResultSet.pm @@ -661,7 +661,6 @@ 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}, @@ -714,7 +713,6 @@ sub single { } my $attrs = $self->_resolved_attrs_copy; - $attrs->{_virtual_order_by} = $self->_gen_virtual_order; if ($where) { if (defined $attrs->{where}) { @@ -742,15 +740,6 @@ 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 to be used if necessary - -sub _gen_virtual_order { - return [ shift->result_source->primary_columns ]; -} # _is_unique_query # @@ -1155,74 +1144,15 @@ sub count { return $self->search(@_)->count if @_ and defined $_[0]; return scalar @{ $self->get_cache } if $self->get_cache; - 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/; - } - - 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, $add_group_by) = @_; + my $meth = $self->_has_resolved_attr (qw/collapse group_by/) + ? 'count_grouped' + : 'count' + ; my $attrs = $self->_resolved_attrs_copy; my $rsrc = $self->result_source; - # 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/prefetch collapse select +select as +as columns +columns order_by/; - - # 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}.$_" } ($rsrc->primary_columns) ]; - } - - $attrs->{from} = [{ - count_subq => (ref $self)->new ($rsrc, $sub_attrs )->as_query - }]; - - # the subquery replaces this - delete $attrs->{$_} for qw/where bind prefetch collapse distinct group_by having having_bind/; - - return $rsrc->storage->count ($rsrc, $attrs); -} - -sub _count_simple { - my $self = shift; - - my $rsrc = $self->result_source; - - # the attrs supplied here are getting modified, do not reuse below - my $count = $rsrc->storage->count ($rsrc, $self->_resolved_attrs_copy); - return 0 unless $count; - - # need to take offset from resolved attrs - my $attrs = $self->_resolved_attrs; - - $count -= $attrs->{offset} if $attrs->{offset}; - $count = $attrs->{rows} if $attrs->{rows} and $attrs->{rows} < $count; - $count = 0 if ($count < 0); - return $count; + return $rsrc->storage->$meth ($rsrc, $attrs); } sub _bool { @@ -1346,15 +1276,15 @@ sub _rs_update_delete { 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) { @@ -1388,7 +1318,7 @@ sub _rs_update_delete { my $subrs = (ref $self)->new($rsrc, $attrs); - return $self->result_source->storage->subq_update_delete($subrs, $op, $values); + return $self->result_source->storage->_subq_update_delete($subrs, $op, $values); } else { return $rsrc->storage->$op( @@ -1878,14 +1808,14 @@ sub _is_deterministic_value { 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; @@ -1893,7 +1823,7 @@ sub _has_attr { 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}; @@ -1910,7 +1840,7 @@ sub _has_attr { } } - # 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 @@ -1995,7 +1925,22 @@ B: This feature is still experimental. =cut -sub as_query { return shift->cursor->as_query(@_) } +sub as_query { + my $self = shift; + + 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 @@ -2036,8 +1981,10 @@ sub find_or_new { my $self = shift; my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); my $hash = ref $_[0] eq 'HASH' ? shift : {@_}; - my $exists = $self->find($hash, $attrs); - return defined $exists ? $exists : $self->new_result($hash); + if (keys %$hash and my $row = $self->find($hash, $attrs) ) { + return $row; + } + return $self->new_result($hash); } =head2 create @@ -2167,8 +2114,10 @@ sub find_or_create { my $self = shift; my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); my $hash = ref $_[0] eq 'HASH' ? shift : {@_}; - my $exists = $self->find($hash, $attrs); - return defined $exists ? $exists : $self->create($hash); + if (keys %$hash and my $row = $self->find($hash, $attrs) ) { + return $row; + } + return $self->create($hash); } =head2 update_or_create @@ -2493,10 +2442,17 @@ sub _resolve_from { my $source = $self->result_source; my $attrs = $self->{attrs}; - my $from = $attrs->{from} - || [ { $attrs->{alias} => $source->from } ]; + my $from = [ @{ + $attrs->{from} + || + [{ + -source_handle => $source->handle, + -alias => $attrs->{alias}, + $attrs->{alias} => $source->from, + }] + }]; - my $seen = { %{$attrs->{seen_join}||{}} }; + my $seen = { %{$attrs->{seen_join} || {} } }; # we need to take the prefetch the attrs into account before we # ->_resolve_join as otherwise they get lost - captainL @@ -2599,7 +2555,11 @@ sub _resolved_attrs { push( @{ $attrs->{as} }, @$adds ); } - $attrs->{from} ||= [ { $self->{attrs}{alias} => $source->from } ]; + $attrs->{from} ||= [ { + -source_handle => $source->handle, + -alias => $self->{attrs}{alias}, + $self->{attrs}{alias} => $source->from, + } ]; if ( exists $attrs->{join} || exists $attrs->{prefetch} ) { my $join = delete $attrs->{join} || {}; @@ -2630,30 +2590,43 @@ sub _resolved_attrs { $attrs->{order_by} = []; } - my $collapse = $attrs->{collapse} || {}; + # If the order_by is otherwise empty - we will use this for TOP limit + # emulation and the like. + # 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 ]; + + + $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; @@ -2672,7 +2645,7 @@ sub _joinpath_aliases { my $p = $paths; $p = $p->{$_} ||= {} for @{$j->[0]{-join_path}}; - push @{$p->{-join_aliases} }, $j->[0]{-join_alias}; + push @{$p->{-join_aliases} }, $j->[0]{-alias}; } return $paths; @@ -3325,9 +3298,21 @@ with a father in the person table, we could explicitly use C: # 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 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