Working join arguments
Arthur Axel 'fREW' Schmidt [Fri, 18 Mar 2011 17:46:05 +0000 (12:46 -0500)]
Changes
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource.pm
t/relationship/join_args.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 32559b7..80d9475 100644 (file)
--- a/Changes
+++ b/Changes
@@ -5,6 +5,8 @@ Revision history for DBIx::Class
           sets quote_char and name_sep appropriate for your RDBMS
         - Add retrieve_on_insert column info flag, allowing to retrieve any
           column value instead of just autoinc primary keys
+        - Allow specification of some runtime relationship options (see
+          DBIx::Class::ResultSet/relationship_options)
         - All limit dialects (except for the older Top and FetchFirst) are
           now using bind parameters for the limits/offsets, making DBI's
           prepare_cached useful across paged resutsets
index fc6bdd0..32627bc 100644 (file)
@@ -3866,7 +3866,7 @@ L<DBIx::Class::Manual::Cookbook> for details.
 
 =over 4
 
-=item Value: ($rel_name | \@rel_names | \%rel_names)
+=item Value: ($rel_name | \@rel_names | \%rel_names_and_join_opts)
 
 =back
 
@@ -3879,7 +3879,8 @@ example:
     { join => 'artist' }
   );
 
-Can also contain a hash reference to refer to the other relation's relations.
+Can also contain a hash reference to refer to the other relation's relations,
+as well as various built in options for a relationship.
 For example:
 
   package MyApp::Schema::Track;
@@ -3899,6 +3900,35 @@ For example:
     }
   );
 
+=head3 relationship options
+
+Currently there are two relationship options that are supported:
+
+=over 6
+
+=item C<-join_type>
+
+This allows you to override the join type
+
+=item C<-alias>
+
+This allows you to override the join name, presumably for joining the same
+relationship more than once.
+
+=back
+
+For example, one might want to do the following with the listed options:
+
+ my $rs = $rs->search({
+    'friends.like' => 1,
+    'enemies.like' => 0,
+ }, {
+   join => [
+      { relationships => { -alias => 'friends' },
+      { relationships => { -alias => 'enemies', -join_type => 'left },
+   ],
+ });
+
 You need to use the relationship (not the table) name in  conditions,
 because they are aliased as such. The current table is aliased as "me", so
 you need to use me.column_name in order to avoid ambiguity. For example:
index d7bcd72..11a8f7e 100644 (file)
@@ -1424,7 +1424,9 @@ sub _compare_relationship_keys {
 
 # Returns the {from} structure used to express JOIN conditions
 sub _resolve_join {
-  my ($self, $join, $alias, $seen, $jpath, $parent_force_left) = @_;
+  my ($self, $join, $alias, $seen, $jpath, $parent_force_left, $opt) = @_;
+
+  $opt ||= {};
 
   # we need a supplied one, because we do in-place modifications, no returns
   $self->throw_exception ('You must supply a seen hashref as the 3rd argument to _resolve_join')
@@ -1460,10 +1462,23 @@ sub _resolve_join {
         $rel, ($seen->{$rel} && $seen->{$rel} + 1)
       );
 
+      my $val = $join->{$rel};
+      my $opt;
+      if (ref $val && ref $val eq 'HASH') {
+         $opt = {
+            alias => delete $val->{-alias},
+            join_type => delete $val->{-join_type},
+         };
+
+         if (my $fail = first { defined && /^-/ } keys %$val) {
+            die "$fail is not a join argument"
+         }
+      }
+
       push @ret, (
-        $self->_resolve_join($rel, $alias, $seen, [@$jpath], $force_left),
+        $self->_resolve_join($rel, $alias, $seen, [@$jpath], $force_left, $opt),
         $self->related_source($rel)->_resolve_join(
-          $join->{$rel}, $as, $seen, [@$jpath, { $rel => $as }], $force_left
+          $val, $as, $seen, [@$jpath, { $rel => $as }], $force_left
         )
       );
     }
@@ -1475,7 +1490,7 @@ sub _resolve_join {
   }
   else {
     my $count = ++$seen->{$join};
-    my $as = $self->storage->relname_to_table_alias(
+    my $as = $opt->{alias} || $self->storage->relname_to_table_alias(
       $join, ($count > 1 && $count)
     );
 
@@ -1485,9 +1500,9 @@ sub _resolve_join {
     my $rel_src = $self->related_source($join);
     return [ { $as => $rel_src->from,
                -rsrc => $rel_src,
-               -join_type => $parent_force_left
+               -join_type => $opt->{join_type} || ($parent_force_left
                   ? 'left'
-                  : $rel_info->{attrs}{join_type}
+                  : $rel_info->{attrs}{join_type})
                 ,
                -join_path => [@$jpath, { $join => $as } ],
                -is_single => (
diff --git a/t/relationship/join_args.t b/t/relationship/join_args.t
new file mode 100644 (file)
index 0000000..af15d52
--- /dev/null
@@ -0,0 +1,52 @@
+use strict;
+use warnings;
+
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+use DBIC::SqlMakerTest;
+use Test::Exception;
+
+my $schema = DBICTest->init_schema;
+
+dies_ok {
+   my $rsq = $schema->resultset('Artist')->search({
+      'artwork_to_artist.artwork_cd_id' => 5,
+   }, {
+      join => { artwork_to_artist => { -unknown_arg => 'foo' } }
+   })->as_query
+} 'dies on unknown rel args';
+
+lives_ok {
+   my $rsq = $schema->resultset('Artist')->search({
+      'a2a.artwork_cd_id' => 5,
+   }, {
+      join => { artwork_to_artist => { -alias => 'a2a' } }
+   })->as_query
+} 'lives for arg -alias';
+
+lives_ok {
+   my $rsq = $schema->resultset('Artist')->search({
+      'artwork_to_artist.artwork_cd_id' => 5,
+   }, {
+      join => { artwork_to_artist => { -join_type => 'left' } }
+   })->as_query
+} 'lives for arg -join_type';
+
+is_same_sql_bind( $schema->resultset('Artist')->search({
+   'a2a.artwork_cd_id' => 5,
+}, {
+   join => {
+      'artwork_to_artist' => { -alias => 'a2a', -join_type => 'right' }
+   }
+})->as_query,
+'(
+   SELECT me.artistid, me.name, me.rank, me.charfield
+   FROM artist me
+   RIGHT JOIN artwork_to_artist a2a
+     ON a2a.artist_id = me.artistid
+   WHERE ( a2a.artwork_cd_id = ? )
+)', [[ 'a2a.artwork_cd_id' => 5 ]], 'rel is aliased and join-typed correctly'
+);
+
+done_testing;