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=61f8aacc3185794f14982111ba86b0daadad6c38;hpb=e493ecb281730e358d23551d4addc5157b850892;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBIHacks.pm b/lib/DBIx/Class/Storage/DBIHacks.pm index 61f8aac..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' ) { @@ -115,16 +117,30 @@ sub _adjust_select_args_for_complex_prefetch { group_by => ['dummy'], %$inner_attrs, }); - # if a multi-type join was needed in the subquery - add a group_by to simulate the - # collapse in the subq + my $inner_aliastypes = + $self->_resolve_aliastypes_from_select_args( $inner_from, $inner_select, $where, $inner_attrs ); + + # if a multi-type non-selecting (only restricting) join was needed in the subquery + # add a group_by to simulate the collapse in the subq if ( ! $inner_attrs->{group_by} and - first { ! $_->[0]{-is_single} } (@{$inner_from}[1 .. $#$inner_from]) + first { + $inner_aliastypes->{restricting}{$_} + and + ! $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 @@ -183,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}; @@ -191,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 @@ -247,7 +279,11 @@ sub _resolve_aliastypes_from_select_args { # set up a botched SQLA my $sql_maker = $self->sql_maker; - local $sql_maker->{having_bind}; # these are throw away results + # these are throw away results, do not pollute the bind stack + 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 @@ -276,8 +312,8 @@ sub _resolve_aliastypes_from_select_args { }), ], selecting => [ - $self->_extract_order_columns ($attrs->{order_by}, $sql_maker), $sql_maker->_recurse_fields ($select), + ( map { $_->[0] } $self->_extract_order_criteria ($attrs->{order_by}, $sql_maker) ), ], }; @@ -336,6 +372,7 @@ sub _resolve_aliastypes_from_select_args { return $aliases_by_type; } +# This is the engine behind { distinct => 1 } sub _group_over_selection { my ($self, $from, $select, $order_by) = @_; @@ -343,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, $_; @@ -356,17 +396,28 @@ 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. select => [ ... { count => 'foo', -as 'foocount' } ... ] - for my $chunk ($self->_extract_order_columns($order_by)) { + # 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) - my $colinfo = $rs_column_list->{$chunk} - or next; + if (@$_ != 1) { + push @leftovers, $_; + next; + } + my $chunk = $_->[0]; + 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 { @@ -588,7 +639,7 @@ sub _strip_cond_qualifiers { return $cond; } -sub _extract_order_columns { +sub _extract_order_criteria { my ($self, $order_by, $sql_maker) = @_; my $parser = sub { @@ -598,8 +649,9 @@ sub _extract_order_columns { unless wantarray; my @chunks; - for my $chunk (map { ref $_ ? @$_ : $_ } ($sql_maker->_order_by_chunks ($order_by) ) ) { - $chunk =~ s/\s+ (?: ASC|DESC ) \s* $//ix; + for ($sql_maker->_order_by_chunks ($order_by) ) { + my $chunk = ref $_ ? $_ : [ $_ ]; + $chunk->[0] =~ s/\s+ (?: ASC|DESC ) \s* $//ix; push @chunks, $chunk; }