spelling fixes in the documaentation, sholud be gud now ;)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBIHacks.pm
index f8dae36..4b66c4e 100644 (file)
@@ -17,31 +17,38 @@ use Carp::Clan qw/^DBIx::Class/;
 
 #
 # This code will remove non-selecting/non-restricting joins from
-# {from} specs, aiding the RDBMS query optimizer. It will leave any
-# unused type-multi joins, if the amount of returned rows is
-# important (i.e. count without collapse)
+# {from} specs, aiding the RDBMS query optimizer
 #
 sub _prune_unused_joins {
-  my $self = shift;
+  my ($self) = shift;
+
+  my ($from, $select, $where, $attrs) = @_;
 
-  my $from = shift;
   if (ref $from ne 'ARRAY' || ref $from->[0] ne 'HASH' || ref $from->[1] ne 'ARRAY') {
     return $from;   # only standard {from} specs are supported
   }
 
-  my $aliastypes = $self->_resolve_aliases_from_select_args($from, @_);
+  my $aliastypes = $self->_resolve_aliastypes_from_select_args(@_);
+
+  # a grouped set will not be affected by amount of rows. Thus any
+  # {multiplying} joins can go
+  delete $aliastypes->{multiplying} if $attrs->{group_by};
+
 
   my @newfrom = $from->[0]; # FROM head is always present
 
   my %need_joins = (map { %{$_||{}} } (values %$aliastypes) );
   for my $j (@{$from}[1..$#$from]) {
-    push @newfrom, $j if $need_joins{$j->[0]{-alias}};
+    push @newfrom, $j if (
+      (! $j->[0]{-alias}) # legacy crap
+        ||
+      $need_joins{$j->[0]{-alias}}
+    );
   }
 
   return \@newfrom;
 }
 
-
 #
 # This is the code producing joined subqueries like:
 # SELECT me.*, other.* FROM ( SELECT me.* FROM ... ) JOIN other ON ... 
@@ -90,21 +97,16 @@ sub _adjust_select_args_for_complex_prefetch {
   }
 
   # construct the inner $from for the subquery
+  # we need to prune first, because this will determine if we need a group_by below
   my $inner_from = $self->_prune_unused_joins ($from, $inner_select, $where, $inner_attrs);
 
-  # if a multi-type join was needed in the subquery ("multi" is indicated by
-  # presence in {collapse}) - add a group_by to simulate the collapse in the subq
-  unless ($inner_attrs->{group_by}) {
-    for my $alias (map { $_->[0]{-alias} } (@{$inner_from}[1 .. $#$inner_from]) ) {
-
-      # the dot comes from some weirdness in collapse
-      # remove after the rewrite
-      if ($attrs->{collapse}{".$alias"}) {
-        $inner_attrs->{group_by} ||= $inner_select;
-        last;
-      }
-    }
-  }
+  # if a multi-type join was needed in the subquery - add a group_by to simulate the
+  # collapse in the subq
+  $inner_attrs->{group_by} ||= $inner_select
+    if List::Util::first
+      { ! $_->[0]{-is_single} }
+      (@{$inner_from}[1 .. $#$inner_from])
+  ;
 
   # generate the subquery
   my $subq = $self->_select_args_to_query (
@@ -153,7 +155,7 @@ sub _adjust_select_args_for_complex_prefetch {
   # scan the from spec against different attributes, and see which joins are needed
   # in what role
   my $outer_aliastypes =
-    $self->_resolve_aliases_from_select_args( $from, $outer_select, $where, $outer_attrs );
+    $self->_resolve_aliastypes_from_select_args( $from, $outer_select, $where, $outer_attrs );
 
   # see what's left - throw away if not selecting/restricting
   # also throw in a group_by if restricting to guard against
@@ -167,22 +169,7 @@ sub _adjust_select_args_for_complex_prefetch {
     }
     elsif ($outer_aliastypes->{restrict}{$alias}) {
       push @outer_from, $j;
-
-      # FIXME - this should be obviated by SQLA2, as I'll be able to 
-      # have restrict_inner and restrict_outer... or something to that
-      # effect... I think...
-
-      # FIXME2 - I can't find a clean way to determine if a particular join
-      # is a multi - instead I am just treating everything as a potential
-      # explosive join (ribasushi)
-      #
-      # if (my $handle = $j->[0]{-source_handle}) {
-      #   my $rsrc = $handle->resolve;
-      #   ... need to bail out of the following if this is not a multi,
-      #       as it will be much easier on the db ...
-
-          $outer_attrs->{group_by} ||= $outer_select;
-      # }
+      $outer_attrs->{group_by} ||= $outer_select unless $j->[0]{-is_single};
     }
   }
 
@@ -208,7 +195,7 @@ sub _adjust_select_args_for_complex_prefetch {
 # happen is for it to fail due to an unqualified column, which in
 # turn will result in a vocal exception. Qualifying the column will
 # invariably solve the problem.
-sub _resolve_aliases_from_select_args {
+sub _resolve_aliastypes_from_select_args {
   my ( $self, $from, $select, $where, $attrs ) = @_;
 
   $self->throw_exception ('Unable to analyze custom {from}')
@@ -219,10 +206,15 @@ sub _resolve_aliases_from_select_args {
 
   # see what aliases are there to work with
   my $alias_list;
-  my @from = @$from; # if I don't copy weird shit happens
-  for my $j (@from) {
+  for (@$from) {
+    my $j = $_;
     $j = $j->[0] if ref $j eq 'ARRAY';
-    $alias_list->{$j->{-alias}} = $j;
+    my $al = $j->{-alias}
+      or next;
+
+    $alias_list->{$al} = $j;
+    $aliases_by_type->{multiplying}{$al} = 1
+      unless $j->{-is_single};
   }
 
   # set up a botched SQLA
@@ -236,10 +228,7 @@ sub _resolve_aliases_from_select_args {
   my $group_by_sql = $sql_maker->_order_by({
     map { $_ => $attrs->{$_} } qw/group_by having/
   });
-  my @order_by_chunks = (map
-    { ref $_ ? $_->[0] : $_ }
-    $sql_maker->_order_by_chunks ($attrs->{order_by})
-  );
+  my @order_by_chunks = ($self->_parse_order_by ($attrs->{order_by}) );
 
   # match every alias to the sql chunks above
   for my $alias (keys %$alias_list) {
@@ -269,7 +258,7 @@ sub _resolve_aliases_from_select_args {
   for my $type (keys %$aliases_by_type) {
     for my $alias (keys %{$aliases_by_type->{$type}}) {
       $aliases_by_type->{$type}{$_} = 1
-        for (@{ $alias_list->{$alias}{-join_path} || [] });
+        for (map { keys %$_ } @{ $alias_list->{$alias}{-join_path} || [] });
     }
   }
 
@@ -414,7 +403,7 @@ sub _straight_join_to_node {
   # anyway, and deep cloning is just too fucking expensive
   # So replace the first hashref in the node arrayref manually 
   my @new_from = ($from->[0]);
-  my $sw_idx = { map { $_ => 1 } @$switch_branch };
+  my $sw_idx = { map { values %$_ => 1 } @$switch_branch };
 
   for my $j (@{$from}[1 .. $#$from]) {
     my $jalias = $j->[0]{-alias};
@@ -467,13 +456,17 @@ sub _strip_cond_qualifiers {
        for (my $i = 0; $i < @cond; $i++) {
         my $entry = $cond[$i];
         my $hash;
-        if (ref $entry eq 'HASH') {
+        my $ref = ref $entry;
+        if ($ref eq 'HASH' or $ref eq 'ARRAY') {
           $hash = $self->_strip_cond_qualifiers($entry);
         }
-        else {
+        elsif (! $ref) {
           $entry =~ /([^.]+)$/;
           $hash->{$1} = $cond[++$i];
         }
+        else {
+          $self->throw_exception ("_strip_cond_qualifiers() is unable to handle a condition reftype $ref");
+        }
         push @{$cond->{-and}}, $hash;
       }
     }
@@ -491,5 +484,21 @@ sub _strip_cond_qualifiers {
   return $cond;
 }
 
+sub _parse_order_by {
+  my ($self, $order_by) = @_;
+
+  return scalar $self->sql_maker->_order_by_chunks ($order_by)
+    unless wantarray;
+
+  my $sql_maker = $self->sql_maker;
+  local $sql_maker->{quote_char}; #disable quoting
+  my @chunks;
+  for my $chunk (map { ref $_ ? @$_ : $_ } ($sql_maker->_order_by_chunks ($order_by) ) ) {
+    $chunk =~ s/\s+ (?: ASC|DESC ) \s* $//ix;
+    push @chunks, $chunk;
+  }
+
+  return @chunks;
+}
 
 1;