Fix proper handling of composite resultset heads (e.g. as_subselect_rs)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSet.pm
index 6a2777f..91ae0bb 100644 (file)
@@ -988,13 +988,14 @@ L<DBIx::Class::Cursor> for more information.
 =cut
 
 sub cursor {
-  my ($self) = @_;
-
-  my $attrs = $self->_resolved_attrs_copy;
+  my $self = shift;
 
-  return $self->{cursor}
-    ||= $self->result_source->storage->select($attrs->{from}, $attrs->{select},
-          $attrs->{where},$attrs);
+  return $self->{cursor} ||= do {
+    my $attrs = { %{$self->_resolved_attrs } };
+    $self->result_source->storage->select(
+      $attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
+    );
+  };
 }
 
 =head2 single
@@ -1046,7 +1047,7 @@ sub single {
       $self->throw_exception('single() only takes search conditions, no attributes. You want ->search( $cond, $attrs )->single()');
   }
 
-  my $attrs = $self->_resolved_attrs_copy;
+  my $attrs = { %{$self->_resolved_attrs} };
 
   if (keys %{$attrs->{collapse}}) {
     $self->throw_exception(
@@ -1455,7 +1456,7 @@ sub count {
   return $self->search(@_)->count if @_ and defined $_[0];
   return scalar @{ $self->get_cache } if $self->get_cache;
 
-  my $attrs = $self->_resolved_attrs_copy;
+  my $attrs = { %{ $self->_resolved_attrs } };
 
   # this is a little optimization - it is faster to do the limit
   # adjustments in software, instead of a subquery
@@ -1720,7 +1721,6 @@ another query.
 
 sub reset {
   my ($self) = @_;
-  delete $self->{_attrs} if exists $self->{_attrs};
   $self->{all_cache_position} = 0;
   $self->cursor->reset;
   return $self;
@@ -1761,37 +1761,47 @@ sub _rs_update_delete {
 
   my $attrs = { %{$self->_resolved_attrs} };
 
+  my $join_classifications;
   my $existing_group_by = delete $attrs->{group_by};
-  my $needs_subq = defined $existing_group_by;
 
-  # simplify the joinmap and maybe decide if a subquery is necessary
-  my $relation_classifications = {};
+  # do we need a subquery for any reason?
+  my $needs_subq = (
+    defined $existing_group_by
+      or
+    # if {from} is unparseable wrap a subq
+    ref($attrs->{from}) ne 'ARRAY'
+      or
+    # limits call for a subq
+    $self->_has_resolved_attr(qw/rows offset/)
+  );
 
-  if (ref($attrs->{from}) eq 'ARRAY') {
-    # if we already know we need a subq, no point of classifying relations
-    if (!$needs_subq and @{$attrs->{from}} > 1) {
-      $attrs->{from} = $storage->_prune_unused_joins ($attrs->{from}, $attrs->{select}, $cond, $attrs);
+  # simplify the joinmap, so we can further decide if a subq is necessary
+  if (!$needs_subq and @{$attrs->{from}} > 1) {
+    $attrs->{from} = $storage->_prune_unused_joins ($attrs->{from}, $attrs->{select}, $cond, $attrs);
 
-      $relation_classifications = $storage->_resolve_aliastypes_from_select_args (
+    # check if there are any joins left after the prune
+    if ( @{$attrs->{from}} > 1 ) {
+      $join_classifications = $storage->_resolve_aliastypes_from_select_args (
         [ @{$attrs->{from}}[1 .. $#{$attrs->{from}}] ],
         $attrs->{select},
         $cond,
         $attrs
       );
+
+      # any non-pruneable joins imply subq
+      $needs_subq = scalar keys %{ $join_classifications->{restricting} || {} };
     }
   }
-  else {
-    $needs_subq ||= 1; # if {from} is unparseable assume the worst
-  }
+
+  # check if the head is composite (by now all joins are thrown out unless $needs_subq)
+  $needs_subq ||= (
+    (ref $attrs->{from}[0]) ne 'HASH'
+      or
+    ref $attrs->{from}[0]{ $attrs->{from}[0]{-alias} }
+  );
 
   # do we need anything like a subquery?
-  if (
-    ! $needs_subq
-      and
-    ! keys %{ $relation_classifications->{restricting} || {} }
-      and
-    ! $self->_has_resolved_attr(qw/rows offset/) # limits call for a subq
-  ) {
+  unless ($needs_subq) {
     # Most databases do not allow aliasing of tables in UPDATE/DELETE. Thus
     # a condition containing 'me' or other table prefixes will not work
     # at all. Tell SQLMaker to dequalify idents via a gross hack.
@@ -1800,6 +1810,7 @@ sub _rs_update_delete {
       local $sqla->{_dequalify_idents} = 1;
       \[ $sqla->_recurse_where($self->{cond}) ];
     };
+
     return $rsrc->storage->$op(
       $rsrc,
       $op eq 'update' ? $values : (),
@@ -1830,29 +1841,26 @@ sub _rs_update_delete {
     );
   }
   elsif ($storage->_use_multicolumn_in) {
-    # This is hideously ugly, but SQLA does not understand multicol IN expressions
-    my $sql_maker = $storage->sql_maker;
-    my ($sql, @bind) = @${$subrs->as_query};
-    $sql = sprintf ('(%s) IN %s', # the as_query already comes with a set of parenthesis
-      join (', ', map { $sql_maker->_quote ($_) } @$idcols),
-      $sql,
-    );
-
     return $storage->$op (
       $rsrc,
       $op eq 'update' ? $values : (),
-      \[$sql, @bind],
+      # no syntax for calling this properly yet
+      # !!! EXPERIMENTAL API !!! WILL CHANGE !!!
+      $storage->sql_maker->_where_op_multicolumn_in (
+        $idcols, # how do I convey a list of idents...? can binds reside on lhs?
+        $subrs->as_query
+      ),
     );
   }
   else {
 
     # if all else fails - get all primary keys and operate over a ORed set
     # wrap in a transaction for consistency
-    # this is where the group_by starts to matter
+    # this is where the group_by/multiplication starts to matter
     if (
       $existing_group_by
         or
-      keys %{ $relation_classifications->{multiplying} || {} }
+      keys %{ $join_classifications->{multiplying} || {} }
     ) {
       # make sure if there is a supplied group_by it matches the columns compiled above
       # perfectly. Anything else can not be sanely executed on most databases so croak
@@ -2112,10 +2120,7 @@ sub populate {
   return unless @$data;
 
   if(defined wantarray) {
-    my @created;
-    foreach my $item (@$data) {
-      push(@created, $self->create($item));
-    }
+    my @created = map { $self->create($_) } @$data;
     return wantarray ? @created : \@created;
   }
   else {
@@ -2170,14 +2175,12 @@ sub populate {
     ## inherit the data locked in the conditions of the resultset
     my ($rs_data) = $self->_merge_with_rscond({});
     delete @{$rs_data}{@columns};
-    my @inherit_cols = keys %$rs_data;
-    my @inherit_data = values %$rs_data;
 
     ## do bulk insert on current row
     $rsrc->storage->insert_bulk(
       $rsrc,
-      [@columns, @inherit_cols],
-      [ map { [ @$_{@columns}, @inherit_data ] } @$data ],
+      [@columns, keys %$rs_data],
+      [ map { [ @$_{@columns}, values %$rs_data ] } @$data ],
     );
 
     ## do the has_many relationships
@@ -2526,7 +2529,7 @@ This is generally used as the RHS for a subquery.
 sub as_query {
   my $self = shift;
 
-  my $attrs = $self->_resolved_attrs_copy;
+  my $attrs = { %{ $self->_resolved_attrs } };
 
   # For future use:
   #
@@ -3108,9 +3111,7 @@ source alias of the current result set:
 =cut
 
 sub current_source_alias {
-  my ($self) = @_;
-
-  return ($self->{attrs} || {})->{alias} || 'me';
+  return (shift->{attrs} || {})->{alias} || 'me';
 }
 
 =head2 as_subselect_rs
@@ -3292,12 +3293,6 @@ sub _chain_relationship {
   return {%$attrs, from => $from, seen_join => $seen};
 }
 
-# too many times we have to do $attrs = { %{$self->_resolved_attrs} }
-sub _resolved_attrs_copy {
-  my $self = shift;
-  return { %{$self->_resolved_attrs (@_)} };
-}
-
 sub _resolved_attrs {
   my $self = shift;
   return $self->{_attrs} if $self->{_attrs};