X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBIHacks.pm;h=cbf462636ae2ced456bb862e68178d87b64e7147;hb=36fd7f073078f8f36277b467910ab68676361edf;hp=41ca371b5f7f6edec4425d0f173dec6ce7421860;hpb=887a0aefb7a00e6b1ad3265747a1af0d1fd64e5d;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBIHacks.pm b/lib/DBIx/Class/Storage/DBIHacks.pm index 41ca371..cbf4626 100644 --- a/lib/DBIx/Class/Storage/DBIHacks.pm +++ b/lib/DBIx/Class/Storage/DBIHacks.pm @@ -60,7 +60,7 @@ sub _adjust_select_args_for_complex_prefetch { my ($self, $from, $select, $where, $attrs) = @_; $self->throw_exception ('Nothing to prefetch... how did we get here?!') - if not @{$attrs->{_prefetch_select}}; + if not @{$attrs->{_prefetch_selector_range}}; $self->throw_exception ('Complex prefetches are not supported on resultsets with a custom from attribute') if (ref $from ne 'ARRAY' || ref $from->[0] ne 'HASH' || ref $from->[1] ne 'ARRAY'); @@ -71,7 +71,7 @@ sub _adjust_select_args_for_complex_prefetch { delete $outer_attrs->{$_} for qw/where bind rows offset group_by having/; my $inner_attrs = { %$attrs }; - delete $inner_attrs->{$_} for qw/for collapse _prefetch_select _collapse_order_by select as/; + delete $inner_attrs->{$_} for qw/for collapse _prefetch_selector_range _collapse_order_by select as/; # bring over all non-collapse-induced order_by into the inner query (if any) @@ -88,7 +88,9 @@ sub _adjust_select_args_for_complex_prefetch { # on the outside we substitute any function for its alias my $outer_select = [ @$select ]; my $inner_select = []; - for my $i (0 .. ( @$outer_select - @{$outer_attrs->{_prefetch_select}} - 1) ) { + + my ($p_start, $p_end) = @{$outer_attrs->{_prefetch_selector_range}}; + for my $i (0 .. $p_start - 1, $p_end + 1 .. $#$outer_select) { my $sel = $outer_select->[$i]; if (ref $sel eq 'HASH' ) { @@ -129,9 +131,16 @@ sub _adjust_select_args_for_complex_prefetch { ! $inner_aliastypes->{selecting}{$_} } ( keys %{$inner_aliastypes->{multiplying}||{}} ) ) { - $inner_attrs->{group_by} = $self->_group_over_selection ( + my $unprocessed_order_chunks; + ($inner_attrs->{group_by}, $unprocessed_order_chunks) = $self->_group_over_selection ( $inner_from, $inner_select, $inner_attrs->{order_by} ); + + $self->throw_exception ( + 'A required group_by clause could not be constructed automatically due to a complex ' + . 'order_by criteria. Either order_by columns only (no functions) or construct a suitable ' + . 'group_by by hand' + ) if $unprocessed_order_chunks; } # we already optimized $inner_from above @@ -190,6 +199,7 @@ sub _adjust_select_args_for_complex_prefetch { # also throw in a group_by if restricting to guard against # cross-join explosions # + my $need_outer_group_by; while (my $j = shift @$from) { my $alias = $j->[0]{-alias}; @@ -198,13 +208,28 @@ sub _adjust_select_args_for_complex_prefetch { } elsif ($outer_aliastypes->{restricting}{$alias}) { push @outer_from, $j; - $outer_attrs->{group_by} ||= $outer_select unless $j->[0]{-is_single}; + $need_outer_group_by ||= ! $j->[0]{-is_single}; } } # demote the outer_from head $outer_from[0] = $outer_from[0][0]; + if ($need_outer_group_by and ! $outer_attrs->{group_by}) { + + my $unprocessed_order_chunks; + ($outer_attrs->{group_by}, $unprocessed_order_chunks) = $self->_group_over_selection ( + \@outer_from, $outer_select, $outer_attrs->{order_by} + ); + + $self->throw_exception ( + 'A required group_by clause could not be constructed automatically due to a complex ' + . 'order_by criteria. Either order_by columns only (no functions) or construct a suitable ' + . 'group_by by hand' + ) if $unprocessed_order_chunks; + + } + # 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, and even # then if where conditions apply to the *right* side of the prefetch, you may have @@ -255,8 +280,10 @@ sub _resolve_aliastypes_from_select_args { my $sql_maker = $self->sql_maker; # these are throw away results, do not pollute the bind stack - local $sql_maker->{having_bind}; local $sql_maker->{select_bind}; + local $sql_maker->{where_bind}; + local $sql_maker->{group_bind}; + local $sql_maker->{having_bind}; # we can't scan properly without any quoting (\b doesn't cut it # everywhere), so unless there is proper quoting set - use our @@ -353,6 +380,9 @@ sub _group_over_selection { my (@group_by, %group_index); + # the logic is: if it is a { func => val } we assume an aggregate, + # otherwise if \'...' or \[...] we assume the user knows what is + # going on thus group over it for (@$select) { if (! ref($_) or ref ($_) ne 'HASH' ) { push @group_by, $_; @@ -367,17 +397,27 @@ sub _group_over_selection { # add any order_by parts that are not already present in the group_by # we need to be careful not to add any named functions/aggregates # i.e. order_by => [ ... { count => 'foo' } ... ] + my @leftovers; for ($self->_extract_order_criteria($order_by)) { # only consider real columns (for functions the user got to do an explicit group_by) - next if @$_ != 1; + if (@$_ != 1) { + push @leftovers, $_; + next; + } my $chunk = $_->[0]; - my $colinfo = $rs_column_list->{$chunk} or next; + my $colinfo = $rs_column_list->{$chunk} or do { + push @leftovers, $_; + next; + }; $chunk = "$colinfo->{-source_alias}.$chunk" if $chunk !~ /\./; push @group_by, $chunk unless $group_index{$chunk}++; } - return \@group_by; + return wantarray + ? (\@group_by, (@leftovers ? \@leftovers : undef) ) + : \@group_by + ; } sub _resolve_ident_sources {