"search_related: result source '" . $self->result_source->source_name .
"' has no such relationship $rel")
unless $rel_obj;
-
+
my ($from,$seen) = $self->_resolve_from($rel);
my $join_count = $seen->{$rel};
return ($self->{attrs} || {})->{alias} || 'me';
}
+# This code is called by search_related, and makes sure there
+# is clear separation between the joins before, during, and
+# after the relationship. This information is needed later
+# in order to properly resolve prefetch aliases (any alias
+# with a relation_chain_depth less than the depth of the
+# current prefetch is not considered)
sub _resolve_from {
my ($self, $extra_join) = @_;
my $source = $self->result_source;
my $attrs = $self->{attrs};
-
+
my $from = $attrs->{from}
|| [ { $attrs->{alias} => $source->from } ];
my $seen = { %{$attrs->{seen_join}||{}} };
- my $join = ($attrs->{join}
- ? [ $attrs->{join}, $extra_join ]
- : $extra_join);
-
# we need to take the prefetch the attrs into account before we
# ->resolve_join as otherwise they get lost - captainL
- my $merged = $self->_merge_attr( $join, $attrs->{prefetch} );
+ my $merged = $self->_merge_attr( $attrs->{join}, $attrs->{prefetch} );
+
+ push @$from, $source->resolve_join($merged, $attrs->{alias}, $seen) if ($merged);
+
+ ++$seen->{-relation_chain_depth};
+
+ push @$from, $source->resolve_join($extra_join, $attrs->{alias}, $seen);
- $from = [
- @$from,
- ($join ? $source->resolve_join($merged, $attrs->{alias}, $seen) : ()),
- ];
+ ++$seen->{-relation_chain_depth};
return ($from,$seen);
}
if ( my $prefetch = delete $attrs->{prefetch} ) {
$prefetch = $self->_merge_attr( {}, $prefetch );
my @pre_order;
- my $seen = { %{ $attrs->{seen_join} || {} } };
foreach my $p ( ref $prefetch eq 'ARRAY' ? @$prefetch : ($prefetch) ) {
# bring joins back to level of current class
+ my $join_map = $self->_joinpath_aliases ($attrs->{from}, $attrs->{seen_join});
my @prefetch =
- $source->resolve_prefetch( $p, $alias, $seen, \@pre_order, $collapse );
+ $source->resolve_prefetch( $p, $alias, $join_map, \@pre_order, $collapse );
push( @{ $attrs->{select} }, map { $_->[0] } @prefetch );
push( @{ $attrs->{as} }, map { $_->[1] } @prefetch );
}
return $self->{_attrs} = $attrs;
}
+sub _joinpath_aliases {
+ my ($self, $fromspec, $seen) = @_;
+
+ my $paths = {};
+ return $paths unless ref $fromspec eq 'ARRAY';
+
+ for my $j (@$fromspec) {
+
+ next if ref $j ne 'ARRAY';
+ next if $j->[0]{-relation_chain_depth} < ( $seen->{-relation_chain_depth} || 0);
+
+ my $p = $paths;
+ $p = $p->{$_} ||= {} for @{$j->[0]{-join_path}};
+ push @{$p->{-join_aliases} }, $j->[0]{-join_alias};
+ }
+
+ return $paths;
+}
+
sub _rollout_attr {
my ($self, $attr) = @_;
=cut
sub resolve_join {
- my ($self, $join, $alias, $seen, $force_left) = @_;
- $seen ||= {};
+ my ($self, $join, $alias, $seen, $force_left, $jpath) = @_;
+
+ # 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')
+ unless $seen;
+
$force_left ||= { force => 0 };
+ $jpath ||= [];
+
if (ref $join eq 'ARRAY') {
return
map {
local $force_left->{force} = $force_left->{force};
- $self->resolve_join($_, $alias, $seen, $force_left);
+ $self->resolve_join($_, $alias, $seen, $force_left, [@$jpath]);
} @$join;
} elsif (ref $join eq 'HASH') {
return
map {
- my $as = ($seen->{$_} ? $_.'_'.($seen->{$_}+1) : $_);
+ my $as = ($seen->{$_} ? join ('_', $_, $seen->{$_} + 1) : $_); # the actual seen value will be incremented below
local $force_left->{force} = $force_left->{force};
(
- $self->resolve_join($_, $alias, $seen, $force_left),
+ $self->resolve_join($_, $alias, $seen, $force_left, [@$jpath]),
$self->related_source($_)->resolve_join(
- $join->{$_}, $as, $seen, $force_left
+ $join->{$_}, $as, $seen, $force_left, [@$jpath, $_]
)
);
} keys %$join;
} elsif (ref $join) {
$self->throw_exception("No idea how to resolve join reftype ".ref $join);
} else {
+
my $count = ++$seen->{$join};
my $as = ($count > 1 ? "${join}_${count}" : $join);
+
my $rel_info = $self->relationship_info($join);
$self->throw_exception("No such relationship ${join}") unless $rel_info;
my $type;
$force_left->{force} = 1 if lc($type) eq 'left';
}
return [ { $as => $self->related_source($join)->from,
- -join_type => $type },
+ -join_type => $type,
+ -join_path => [@$jpath, $join],
+ -join_alias => $as,
+ -relation_chain_depth => $seen->{-relation_chain_depth} || 0,
+ },
$self->resolve_condition($rel_info->{cond}, $as, $alias) ];
}
}
=cut
sub resolve_prefetch {
- my ($self, $pre, $alias, $seen, $order, $collapse) = @_;
- $seen ||= {};
+ my ($self, $pre, $alias, $alias_map, $order, $collapse, $pref_path) = @_;
+ $pref_path ||= [];
+
if( ref $pre eq 'ARRAY' ) {
return
- map { $self->resolve_prefetch( $_, $alias, $seen, $order, $collapse ) }
+ map { $self->resolve_prefetch( $_, $alias, $alias_map, $order, $collapse, [ @$pref_path ] ) }
@$pre;
}
elsif( ref $pre eq 'HASH' ) {
my @ret =
map {
- $self->resolve_prefetch($_, $alias, $seen, $order, $collapse),
+ $self->resolve_prefetch($_, $alias, $alias_map, $order, $collapse, [ @$pref_path ] ),
$self->related_source($_)->resolve_prefetch(
- $pre->{$_}, "${alias}.$_", $seen, $order, $collapse)
+ $pre->{$_}, "${alias}.$_", $alias_map, $order, $collapse, [ @$pref_path, $_] )
} keys %$pre;
return @ret;
}
"don't know how to resolve prefetch reftype ".ref($pre));
}
else {
- my $count = ++$seen->{$pre};
- my $as = ($count > 1 ? "${pre}_${count}" : $pre);
+
+ my $p = $alias_map;
+ $p = $p->{$_} for (@$pref_path, $pre);
+
+ $self->throw_exception (
+ "Unable to resolve prefetch $pre - join alias map does not contain an entry for path "
+ . join (' -> ', @$pref_path, $pre)
+ ) if (ref $p->{-join_aliases} ne 'ARRAY' or not @{$p->{-join_aliases}} );
+
+ my $as = shift @{$p->{-join_aliases}};
+
my $rel_info = $self->relationship_info( $pre );
$self->throw_exception( $self->name . " has no such relationship '$pre'" )
unless $rel_info;