some support for dq in rs condition merging and populate
Matt S Trout [Fri, 15 Nov 2013 13:14:44 +0000 (13:14 +0000)]
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource.pm
t/lib/DBICTest/Schema/Artist.pm

index 1e73052..071d7e8 100644 (file)
@@ -2338,6 +2338,10 @@ sub populate {
           $rel,
         );
 
+        if (ref($related) eq 'REF' and ref($$related) eq 'HASH') {
+          $related = $self->_extract_fixed_values_for($$related, $rel);
+        }
+
         my @rows_to_add = ref $item->{$rel} eq 'ARRAY' ? @{$item->{$rel}} : ($item->{$rel});
         my @populate = map { {%$_, %$related} } @rows_to_add;
 
@@ -2347,6 +2351,42 @@ sub populate {
   }
 }
 
+sub _extract_fixed_values_for {
+  my ($self, $dq, $alias) = @_;
+  my $fixed = $self->_extract_fixed_conditions_for($dq, $alias);
+  return +{ map {
+    is_Value($fixed->{$_})
+      ? ($_ => $fixed->{$_}{value})
+      : ()
+  } keys %$fixed };
+}
+
+sub _extract_fixed_conditions_for {
+  my ($self, $dq, $alias) = @_;
+  my (@q, %found) = ($dq);
+  while (my $n = shift @q) {
+    if (is_Operator($n)) {
+      if (($n->{operator}{Perl}||'') =~ /^(?:==|eq)$/) {
+        my ($l, $r) = @{$n->{args}};
+        if (
+          is_Identifier($r) and @{$r->{elements}} == 2
+          and $r->{elements}[0] eq $alias
+        ) {
+          ($l, $r) = ($r, $l);
+        }
+        if (
+          is_Identifier($l) and @{$l->{elements}} == 2
+          and $l->{elements}[0] eq $alias
+        ) {
+          $found{$l->{elements}[1]} = $r;
+        } elsif (($n->{operator}{Perl}||'') eq 'and') {
+          push @q, @{$n->{args}};
+        }
+      }
+    }
+  }
+  return \%found;
+}
 
 # populate() arguments went over several incarnations
 # What we ultimately support is AoH
@@ -2516,12 +2556,7 @@ sub _merge_with_rscond {
     %new_data = %{ $self->{attrs}{related_objects} || {} };  # nothing might have been inserted yet
     @cols_from_relations = keys %new_data;
   }
-  elsif (ref $self->{cond} ne 'HASH') {
-    $self->throw_exception(
-      "Can't abstract implicit construct, resultset condition not a hash"
-    );
-  }
-  else {
+  elsif (ref $self->{cond} eq 'HASH') {
     # precedence must be given to passed values over values inherited from
     # the cond, so the order here is important.
     my $collapsed_cond = $self->_collapse_cond($self->{cond});
@@ -2543,6 +2578,14 @@ sub _merge_with_rscond {
       }
     }
   }
+  elsif (ref $self->{cond} eq 'REF' and ref ${$self->{cond}} eq 'HASH') {
+    %new_data = %{$self->_extract_fixed_values_for(${$self->{cond}}, $alias)};
+  }
+  else {
+    $self->throw_exception(
+      "Can't abstract implicit construct, resultset condition not a hash"
+    );
+  }
 
   %new_data = (
     %new_data,
index bad4876..e536cce 100644 (file)
@@ -1498,12 +1498,42 @@ sub reverse_relationship_info {
 
 # all this does is removes the foreign/self prefix from a condition
 sub __strip_relcond {
-  return undef unless ref($_[1]) eq 'HASH';
-  +{
-    map
-      { map { /^ (?:foreign|self) \. (\w+) $/x } ($_, $_[1]{$_}) }
-      keys %{$_[1]}
+  if (ref($_[1]) eq 'HASH') {
+    return +{
+      map
+        { map { /^ (?:foreign|self) \. (\w+) $/x } ($_, $_[1]{$_}) }
+        keys %{$_[1]}
+    };
+  } elsif (blessed($_[1]) and $_[1]->isa('Data::Query::ExprBuilder')) {
+    my (@q, %found) = ($_[1]->{expr});
+    Q: while (my $n = shift @q) {
+      if (is_Operator($n)) {
+        if (($n->{operator}{Perl}||'') =~ /^(?:==|eq)$/) {
+          my ($l, $r) = @{$n->{args}};
+          if (
+            is_Identifier($l) and @{$l->{elements}} == 2
+            and is_Identifier($r) and @{$r->{elements}} == 2
+          ) {
+            ($l, $r) = ($r, $l) if $l->{elements}[0] eq 'self';
+            if (
+              $l->{elements}[0] eq 'foreign'
+              and $r->{elements}[0] eq 'self'
+            ) {
+              $found{$l->{elements}[1]} = $r->{elements}[1];
+              next Q;
+            }
+          }
+        } elsif (($n->{operator}{Perl}||'') eq 'and') {
+          push @q, @{$n->{args}};
+          next Q;
+        }
+      }
+      # didn't match as 'and' or 'foreign.x = self.y', can't handle this
+      return undef;
+    }
+    return \%found;
   }
+  return undef;
 }
 
 sub compare_relationship_keys {
index 34532dc..82423fd 100644 (file)
@@ -6,6 +6,7 @@ use strict;
 
 use base qw/DBICTest::BaseResult/;
 use Carp qw/confess/;
+use Data::Query::ExprDeclare;
 
 __PACKAGE__->table('artist');
 __PACKAGE__->source_info({
@@ -47,7 +48,8 @@ __PACKAGE__->mk_classdata('field_name_for', {
 # the undef condition in this rel is *deliberate*
 # tests oddball legacy syntax
 __PACKAGE__->has_many(
-    cds => 'DBICTest::Schema::CD', undef,
+    cds => 'DBICTest::Schema::CD',
+    expr { $_->foreign->artist == $_->self->artistid },
     { order_by => { -asc => 'year'} },
 );