'bool' => "_bool",
fallback => 1;
use Carp::Clan qw/^DBIx::Class/;
+use DBIx::Class::Exception;
use Data::Page;
use Storable;
use DBIx::Class::ResultSetColumn;
my $where = $self->_collapse_cond($self->{attrs}{where} || {});
my $num_where = scalar keys %$where;
- my @unique_queries;
+ my (@unique_queries, %seen_column_combinations);
foreach my $name (@constraint_names) {
- my @unique_cols = $self->result_source->unique_constraint_columns($name);
- my $unique_query = $self->_build_unique_query($query, \@unique_cols);
+ my @constraint_cols = $self->result_source->unique_constraint_columns($name);
- my $num_cols = scalar @unique_cols;
+ my $constraint_sig = join "\x00", sort @constraint_cols;
+ next if $seen_column_combinations{$constraint_sig}++;
+
+ my $unique_query = $self->_build_unique_query($query, \@constraint_cols);
+
+ my $num_cols = scalar @constraint_cols;
my $num_query = scalar keys %$unique_query;
my $total = $num_query + $num_where;
my $sub_attrs = { %$attrs };
# extra selectors do not go in the subquery and there is no point of ordering it
- delete $sub_attrs->{$_} for qw/collapse prefetch_select select as order_by/;
+ delete $sub_attrs->{$_} for qw/collapse select _prefetch_select as order_by/;
- # if we prefetch, we group_by primary keys only as this is what we would get out of the rs via ->next/->all
- # clobber old group_by regardless
+ # if we prefetch, we group_by primary keys only as this is what we would get out
+ # of the rs via ->next/->all. We DO WANT to clobber old group_by regardless
if ( keys %{$attrs->{collapse}} ) {
$sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } ($rsrc->primary_columns) ]
}
$sub_attrs->{from}, $sub_attrs->{alias}
);
+ # this is so that ordering can be thrown away in things like Top limit
+ $sub_attrs->{-for_count_only} = 1;
+
+ my $sub_rs = $rsrc->resultset_class->new ($rsrc, $sub_attrs);
+
$attrs->{from} = [{
- count_subq => $rsrc->resultset_class->new ($rsrc, $sub_attrs )->as_query
+ -alias => 'count_subq',
+ -source_handle => $rsrc->handle,
+ count_subq => $sub_rs->as_query,
}];
# the subquery replaces this
sub _switch_to_inner_join_if_needed {
my ($self, $from, $alias) = @_;
+ # subqueries and other oddness is naturally not supported
return $from if (
ref $from ne 'ARRAY'
||
+ @$from <= 1
+ ||
ref $from->[0] ne 'HASH'
||
! $from->[0]{-alias}
$from->[0]{-alias} eq $alias
);
- # this would be the case with a subquery - we'll never find
- # the target as it is not in the parseable part of {from}
- return $from if @$from == 1;
-
- my (@switch_idx, $found_target);
-
+ my $switch_branch;
JOINSCAN:
- for my $i (1 .. $#$from) {
-
- push @switch_idx, $i;
- my $j = $from->[$i];
- my $jalias = $j->[0]{-alias};
-
- # we found our current target - delete any siblings (same level joins)
- # and bail out
- if ($jalias eq $alias) {
- $found_target++;
-
- my $cur_depth = $j->[0]{-relation_chain_depth};
- # we are -1, so look at -2
- while (@switch_idx > 1
- && $from->[$switch_idx[-2]][0]{-relation_chain_depth} == $cur_depth
- ) {
- splice @switch_idx, -2, 1;
- }
-
+ for my $j (@{$from}[1 .. $#$from]) {
+ if ($j->[0]{-alias} eq $alias) {
+ $switch_branch = $j->[0]{-join_path};
last JOINSCAN;
}
}
# something else went wrong
- return $from unless $found_target;
+ return $from unless $switch_branch;
# So it looks like we will have to switch some stuff around.
# local() is useless here as we will be leaving the scope
# anyway, and deep cloning is just too fucking expensive
# So replace the inner hashref manually
- my @new_from;
- my $sw_idx = { map { $_ => 1 } @switch_idx };
+ my @new_from = ($from->[0]);
+ my $sw_idx = { map { $_ => 1 } @$switch_branch };
- for my $i (0 .. $#$from) {
- if ($sw_idx->{$i}) {
- my %attrs = %{$from->[$i][0]};
- delete $attrs{-join_type};
+ for my $j (@{$from}[1 .. $#$from]) {
+ my $jalias = $j->[0]{-alias};
+ if ($sw_idx->{$jalias}) {
+ my %attrs = %{$j->[0]};
+ delete $attrs{-join_type};
push @new_from, [
\%attrs,
- @{$from->[$i]}[ 1 .. $#{$from->[$i]} ],
+ @{$j}[ 1 .. $#$j ],
];
}
else {
- push @new_from, $from->[$i];
+ push @new_from, $j;
}
}
if (my $g = $attrs->{group_by}) {
my @current_group_by = map
{ $_ =~ /\./ ? $_ : "$attrs->{alias}.$_" }
- (ref $g eq 'ARRAY' ? @$g : $g );
+ @$g
+ ;
if (
join ("\x00", sort @current_group_by)
}
}
- ## do bulk insert on current row
- my @values = map { [ @$_{@names} ] } @$data;
+ ## merge with the conditions from $self (inherited conditions)
+ my ($inherited_cond) = $self->_merge_with_cond({});
+ delete @{$inherited_cond}{@names};
+ my @inherited_names = keys %$inherited_cond;
+ my @values;
+ foreach my $row (@$data) {
+ my %row_data;
+ @row_data{@names} = @{$row}{@names};
+ my ($merged_cond) = $self->_merge_with_cond(\%row_data);
+ push @values, [ @{$merged_cond}{@names, @inherited_names} ];
+ }
+ push @names, @inherited_names;
+ ## do bulk insert on current row
$self->result_source->storage->insert_bulk(
$self->result_source,
\@names,
$self->throw_exception( "new_result needs a hash" )
unless (ref $values eq 'HASH');
- my %new;
+ my ($merged_cond, $from_resultset) = $self->_merge_with_cond($values);
+
+ my %new = (
+ %$merged_cond,
+ @$from_resultset
+ ? (-from_resultset => $from_resultset)
+ : (),
+ -source_handle => $self->_source_handle,
+ -result_source => $self->result_source, # DO NOT REMOVE THIS, REQUIRED
+ );
+
+ return $self->result_class->new(\%new);
+}
+
+# _merge_with_cond
+#
+# Merges $values (a hashref) with the condition in the resultset and returns
+# the resulting hashref and an arrayref that contains the keys that are coming
+# from related resultsets.
+
+sub _merge_with_cond {
+ my ($self, $values) = @_;
+
+ my (%merged_cond, @from_resultset);
+
my $alias = $self->{attrs}{alias};
if (
defined $self->{cond}
&& $self->{cond} eq $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION
) {
- %new = %{ $self->{attrs}{related_objects} || {} }; # nothing might have been inserted yet
- $new{-from_resultset} = [ keys %new ] if keys %new;
+ %merged_cond = %{ $self->{attrs}{related_objects} || {} }; # nothing might have been inserted yet
+ @from_resultset = keys %merged_cond;
} else {
$self->throw_exception(
"Can't abstract implicit construct, condition not a hash"
# precendence must be given to passed values over values inherited from
# the cond, so the order here is important.
- my %implied = %{$self->_remove_alias($collapsed_cond, $alias)};
- while( my($col,$value) = each %implied ){
- if(ref($value) eq 'HASH' && keys(%$value) && (keys %$value)[0] eq '='){
- $new{$col} = $value->{'='};
+ my %implied = %{$self->_remove_alias($collapsed_cond, $alias)};
+ while ( my($col, $value) = each %implied ) {
+ if (ref($value) eq 'HASH' && keys(%$value) && (keys %$value)[0] eq '=') {
+ $merged_cond{$col} = $value->{'='};
next;
}
- $new{$col} = $value if $self->_is_deterministic_value($value);
+ $merged_cond{$col} = $value if $self->_is_deterministic_value($value);
}
}
- %new = (
- %new,
+ %merged_cond = (
+ %merged_cond,
%{ $self->_remove_alias($values, $alias) },
- -source_handle => $self->_source_handle,
- -result_source => $self->result_source, # DO NOT REMOVE THIS, REQUIRED
);
- return $self->result_class->new(\%new);
+ return (\%merged_cond, \@from_resultset);
}
# _is_deterministic_value
a unique constraint that is not the primary key, or looking for
related rows.
-If you want objects to be saved immediately, use L</find_or_create> instead.
+If you want objects to be saved immediately, use L</find_or_create>
+instead.
-B<Note>: C<find_or_new> is probably not what you want when creating a
-new row in a table that uses primary keys supplied by the
-database. Passing in a primary key column with a value of I<undef>
-will cause L</find> to attempt to search for a row with a value of
-I<NULL>.
+B<Note>: Take care when using C<find_or_new> with a table having
+columns with default values that you intend to be automatically
+supplied by the database (e.g. an auto_increment primary key column).
+In normal usage, the value of such columns should NOT be included at
+all in the call to C<find_or_new>, even when set to C<undef>.
=cut
can also be passed an object representing the foreign row, and the
value will be set to its primary key.
-To create related objects, pass a hashref for the value if the related
-item is a foreign key relationship (L<DBIx::Class::Relationship/belongs_to>),
-and use the name of the relationship as the key. (NOT the name of the field,
-necessarily). For C<has_many> and C<has_one> relationships, pass an arrayref
-of hashrefs containing the data for each of the rows to create in the foreign
-tables, again using the relationship name as the key.
+To create related objects, pass a hashref of related-object column values
+B<keyed on the relationship name>. If the relationship is of type C<multi>
+(L<DBIx::Class::Relationship/has_many>) - pass an arrayref of hashrefs.
+The process will correctly identify columns holding foreign keys, and will
+transparrently populate them from the keys of the corresponding relation.
+This can be applied recursively, and will work correctly for a structure
+with an arbitrary depth and width, as long as the relationships actually
+exists and the correct column data has been supplied.
+
Instead of hashrefs of plain related data (key/value pairs), you may
also pass new or inserted objects. New objects (not inserted yet, see
}
});
+=over
+
+=item WARNING
+
+When subclassing ResultSet never attempt to override this method. Since
+it is a simple shortcut for C<< $self->new_result($attrs)->insert >>, a
+lot of the internals simply never call it, so your override will be
+bypassed more often than not. Override either L<new|DBIx::Class::Row/new>
+or L<insert|DBIx::Class::Row/insert> depending on how early in the
+L</create> process you need to intervene.
+
+=back
+
=cut
sub create {
the find has completed and before the create has started. To avoid
this problem, use find_or_create() inside a transaction.
-B<Note>: C<find_or_create> is probably not what you want when creating
-a new row in a table that uses primary keys supplied by the
-database. Passing in a primary key column with a value of I<undef>
-will cause L</find> to attempt to search for a row with a value of
-I<NULL>.
+B<Note>: Take care when using C<find_or_create> with a table having
+columns with default values that you intend to be automatically
+supplied by the database (e.g. an auto_increment primary key column).
+In normal usage, the value of such columns should NOT be included at
+all in the call to C<find_or_create>, even when set to C<undef>.
See also L</find> and L</update_or_create>. For information on how to declare
unique constraints, see L<DBIx::Class::ResultSource/add_unique_constraint>.
See also L</find> and L</find_or_create>. For information on how to declare
unique constraints, see L<DBIx::Class::ResultSource/add_unique_constraint>.
-B<Note>: C<update_or_create> is probably not what you want when
-looking for a row in a table that uses primary keys supplied by the
-database, unless you actually have a key value. Passing in a primary
-key column with a value of I<undef> will cause L</find> to attempt to
-search for a row with a value of I<NULL>.
+B<Note>: Take care when using C<update_or_create> with a table having
+columns with default values that you intend to be automatically
+supplied by the database (e.g. an auto_increment primary key column).
+In normal usage, the value of such columns should NOT be included at
+all in the call to C<update_or_create>, even when set to C<undef>.
=cut
$cd->insert;
}
-See also L</find>, L</find_or_create> and L<find_or_new>.
+B<Note>: Take care when using C<update_or_new> with a table having
+columns with default values that you intend to be automatically
+supplied by the database (e.g. an auto_increment primary key column).
+In normal usage, the value of such columns should NOT be included at
+all in the call to C<update_or_new>, even when set to C<undef>.
+
+See also L</find>, L</find_or_create> and L</find_or_new>.
=cut
# 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)
+#
+# The increments happen in 1/2s to make it easier to correlate the
+# join depth with the join path. An integer means a relationship
+# specified via a search_related, whereas a fraction means an added
+# join/prefetch via attributes
sub _chain_relationship {
my ($self, $rel) = @_;
my $source = $self->result_source;
}];
my $seen = { %{$attrs->{seen_join} || {} } };
+ my $jpath = ($attrs->{seen_join} && keys %{$attrs->{seen_join}})
+ ? $from->[-1][0]{-join_path}
+ : [];
+
# 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( $attrs->{join}, $attrs->{prefetch} );
- my @requested_joins = $source->_resolve_join($merged, $attrs->{alias}, $seen);
+ my @requested_joins = $source->_resolve_join(
+ $merged,
+ $attrs->{alias},
+ $seen,
+ $jpath,
+ );
push @$from, @requested_joins;
- ++$seen->{-relation_chain_depth};
+ $seen->{-relation_chain_depth} += 0.5;
# if $self already had a join/prefetch specified on it, the requested
# $rel might very well be already included. What we do in this case
# the join in question so we could tell it *is* the search_related)
my $already_joined;
+
# we consider the last one thus reverse
for my $j (reverse @requested_joins) {
if ($rel eq $j->[0]{-join_path}[-1]) {
- $j->[0]{-relation_chain_depth}++;
+ $j->[0]{-relation_chain_depth} += 0.5;
$already_joined++;
last;
}
}
+
+# alternative way to scan the entire chain - not backwards compatible
+# for my $j (reverse @$from) {
+# next unless ref $j eq 'ARRAY';
+# if ($j->[0]{-join_path} && $j->[0]{-join_path}[-1] eq $rel) {
+# $j->[0]{-relation_chain_depth} += 0.5;
+# $already_joined++;
+# last;
+# }
+# }
+
unless ($already_joined) {
- push @$from, $source->_resolve_join($rel, $attrs->{alias}, $seen);
+ push @$from, $source->_resolve_join(
+ $rel,
+ $attrs->{alias},
+ $seen,
+ $jpath,
+ );
}
- ++$seen->{-relation_chain_depth};
+ $seen->{-relation_chain_depth} += 0.5;
return ($from,$seen);
}
# build columns (as long as select isn't set) into a set of as/select hashes
unless ( $attrs->{select} ) {
- @colbits = map {
- ( ref($_) eq 'HASH' )
- ? $_
- : {
- (
- /^\Q${alias}.\E(.+)$/
- ? "$1"
- : "$_"
- )
- =>
- (
- /\./
- ? "$_"
- : "${alias}.$_"
- )
- }
- } ( ref($attrs->{columns}) eq 'ARRAY' ) ? @{ delete $attrs->{columns}} : (delete $attrs->{columns} || $source->columns );
+
+ my @cols = ( ref($attrs->{columns}) eq 'ARRAY' )
+ ? @{ delete $attrs->{columns}}
+ : (
+ ( delete $attrs->{columns} )
+ ||
+ $source->columns
+ )
+ ;
+
+ @colbits = map {
+ ( ref($_) eq 'HASH' )
+ ? $_
+ : {
+ (
+ /^\Q${alias}.\E(.+)$/
+ ? "$1"
+ : "$_"
+ )
+ =>
+ (
+ /\./
+ ? "$_"
+ : "${alias}.$_"
+ )
+ }
+ } @cols;
}
+
# add the additional columns on
foreach ( 'include_columns', '+columns' ) {
push @colbits, map {
if ( $attrs->{join} || $attrs->{prefetch} ) {
- $self->throw_exception ('join/prefetch can not be used with a literal scalarref {from}')
+ $self->throw_exception ('join/prefetch can not be used with a custom {from}')
if ref $attrs->{from} ne 'ARRAY';
my $join = delete $attrs->{join} || {};
[
@{ $attrs->{from} },
$source->_resolve_join(
- $join, $alias, { %{ $attrs->{seen_join} || {} } }
+ $join,
+ $alias,
+ { %{ $attrs->{seen_join} || {} } },
+ ($attrs->{seen_join} && keys %{$attrs->{seen_join}})
+ ? $attrs->{from}[-1][0]{-join_path}
+ : []
+ ,
)
];
}
- if ( $attrs->{order_by} ) {
+ if ( defined $attrs->{order_by} ) {
$attrs->{order_by} = (
ref( $attrs->{order_by} ) eq 'ARRAY'
? [ @{ $attrs->{order_by} } ]
- : [ $attrs->{order_by} ]
+ : [ $attrs->{order_by} || () ]
);
}
- if ($attrs->{group_by} and ! ref $attrs->{group_by}) {
+ if ($attrs->{group_by} and ref $attrs->{group_by} ne 'ARRAY') {
$attrs->{group_by} = [ $attrs->{group_by} ];
}
- # If the order_by is otherwise empty - we will use this for TOP limit
- # emulation and the like.
- # Although this is needed only if the order_by is not defined, it is
- # actually cheaper to just populate this rather than properly examining
- # order_by (stuf like [ {} ] and the like)
- $attrs->{_virtual_order_by} = [ $self->result_source->primary_columns ];
-
+ # generate the distinct induced group_by early, as prefetch will be carried via a
+ # subquery (since a group_by is present)
+ if (delete $attrs->{distinct}) {
+ if ($attrs->{group_by}) {
+ carp ("Useless use of distinct on a grouped resultset ('distinct' is ignored when a 'group_by' is present)");
+ }
+ else {
+ $attrs->{group_by} = [ grep { !ref($_) || (ref($_) ne 'HASH') } @{$attrs->{select}} ];
+ }
+ }
$attrs->{collapse} ||= {};
if ( my $prefetch = delete $attrs->{prefetch} ) {
my @prefetch =
$source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} );
- $attrs->{prefetch_select} = [ map { $_->[0] } @prefetch ];
- push @{ $attrs->{select} }, @{$attrs->{prefetch_select}};
+ # we need to somehow mark which columns came from prefetch
+ $attrs->{_prefetch_select} = [ map { $_->[0] } @prefetch ];
+
+ push @{ $attrs->{select} }, @{$attrs->{_prefetch_select}};
push @{ $attrs->{as} }, (map { $_->[1] } @prefetch);
- push( @{ $attrs->{order_by} }, @$prefetch_ordering );
+ push( @{$attrs->{order_by}}, @$prefetch_ordering );
$attrs->{_collapse_order_by} = \@$prefetch_ordering;
}
-
- if (delete $attrs->{distinct}) {
- $attrs->{group_by} ||= [ grep { !ref($_) || (ref($_) ne 'HASH') } @{$attrs->{select}} ];
- }
-
# if both page and offset are specified, produce a combined offset
# even though it doesn't make much sense, this is what pre 081xx has
# been doing
if (my $page = delete $attrs->{page}) {
- $attrs->{offset} = ($attrs->{rows} * ($page - 1)) +
- ($attrs->{offset} || 0);
+ $attrs->{offset} =
+ ($attrs->{rows} * ($page - 1))
+ +
+ ($attrs->{offset} || 0)
+ ;
}
return $self->{_attrs} = $attrs;
my $paths = {};
return $paths unless ref $fromspec eq 'ARRAY';
+ my $cur_depth = $seen->{-relation_chain_depth} || 0;
+
+ if (int ($cur_depth) != $cur_depth) {
+ $self->throw_exception ("-relation_chain_depth is not an integer, something went horribly wrong ($cur_depth)");
+ }
+
for my $j (@$fromspec) {
next if ref $j ne 'ARRAY';
- next if $j->[0]{-relation_chain_depth} < ( $seen->{-relation_chain_depth} || 0);
+ next if ($j->[0]{-relation_chain_depth} || 0) < $cur_depth;
+
+ my $jpath = $j->[0]{-join_path};
my $p = $paths;
- $p = $p->{$_} ||= {} for @{$j->[0]{-join_path}};
+ $p = $p->{$_} ||= {} for @{$jpath}[$cur_depth .. $#$jpath];
push @{$p->{-join_aliases} }, $j->[0]{-alias};
}
sub _calculate_score {
my ($self, $a, $b) = @_;
+ if (defined $a xor defined $b) {
+ return 0;
+ }
+ elsif (not defined $a) {
+ return 1;
+ }
+
if (ref $b eq 'HASH') {
my ($b_key) = keys %{$b};
if (ref $a eq 'HASH') {
sub throw_exception {
my $self=shift;
+
if (ref $self && $self->_source_handle->schema) {
$self->_source_handle->schema->throw_exception(@_)
- } else {
- croak(@_);
}
-
+ else {
+ DBIx::Class::Exception->throw(@_);
+ }
}
# XXX: FIXME: Attributes docs need clearing up
=back
-Which column(s) to order the results by. If a single column name, or
-an arrayref of names is supplied, the argument is passed through
-directly to SQL. The hashref syntax allows for connection-agnostic
-specification of ordering direction:
+Which column(s) to order the results by.
+
+[The full list of suitable values is documented in
+L<SQL::Abstract/"ORDER BY CLAUSES">; the following is a summary of
+common options.]
+
+If a single column name, or an arrayref of names is supplied, the
+argument is passed through directly to SQL. The hashref syntax allows
+for connection-agnostic specification of ordering direction:
For descending order:
B<NOTE:> If you specify a C<prefetch> attribute, the C<join> and C<select>
attributes will be ignored.
+B<CAVEATs>: Prefetch does a lot of deep magic. As such, it may not behave
+exactly as you might expect.
+
+=over 4
+
+=item *
+
+Prefetch uses the L</cache> to populate the prefetched relationships. This
+may or may not be what you want.
+
+=item *
+
+If you specify a condition on a prefetched relationship, ONLY those
+rows that match the prefetched condition will be fetched into that relationship.
+This means that adding prefetch to a search() B<may alter> what is returned by
+traversing a relationship. So, if you have C<< Artist->has_many(CDs) >> and you do
+
+ my $artist_rs = $schema->resultset('Artist')->search({
+ 'cds.year' => 2008,
+ }, {
+ join => 'cds',
+ });
+
+ my $count = $artist_rs->first->cds->count;
+
+ my $artist_rs_prefetch = $artist_rs->search( {}, { prefetch => 'cds' } );
+
+ my $prefetch_count = $artist_rs_prefetch->first->cds->count;
+
+ cmp_ok( $count, '==', $prefetch_count, "Counts should be the same" );
+
+that cmp_ok() may or may not pass depending on the datasets involved. This
+behavior may or may not survive the 0.09 transition.
+
+=back
+
=head2 page
=over 4
=back
-Set to 1 to group by all columns.
+Set to 1 to group by all columns. If the resultset already has a group_by
+attribute, this setting is ignored and an appropriate warning is issued.
=head2 where
For more examples of using these attributes, see
L<DBIx::Class::Manual::Cookbook>.
-=head2 from
-
-=over 4
-
-=item Value: \@from_clause
-
-=back
-
-The C<from> attribute gives you manual control over the C<FROM> clause of SQL
-statements generated by L<DBIx::Class>, allowing you to express custom C<JOIN>
-clauses.
-
-NOTE: Use this on your own risk. This allows you to shoot off your foot!
-
-C<join> will usually do what you need and it is strongly recommended that you
-avoid using C<from> unless you cannot achieve the desired result using C<join>.
-And we really do mean "cannot", not just tried and failed. Attempting to use
-this because you're having problems with C<join> is like trying to use x86
-ASM because you've got a syntax error in your C. Trust us on this.
-
-Now, if you're still really, really sure you need to use this (and if you're
-not 100% sure, ask the mailing list first), here's an explanation of how this
-works.
-
-The syntax is as follows -
-
- [
- { <alias1> => <table1> },
- [
- { <alias2> => <table2>, -join_type => 'inner|left|right' },
- [], # nested JOIN (optional)
- { <table1.column1> => <table2.column2>, ... (more conditions) },
- ],
- # More of the above [ ] may follow for additional joins
- ]
-
- <table1> <alias1>
- JOIN
- <table2> <alias2>
- [JOIN ...]
- ON <table1.column1> = <table2.column2>
- <more joins may follow>
-
-An easy way to follow the examples below is to remember the following:
-
- Anything inside "[]" is a JOIN
- Anything inside "{}" is a condition for the enclosing JOIN
-
-The following examples utilize a "person" table in a family tree application.
-In order to express parent->child relationships, this table is self-joined:
-
- # Person->belongs_to('father' => 'Person');
- # Person->belongs_to('mother' => 'Person');
-
-C<from> can be used to nest joins. Here we return all children with a father,
-then search against all mothers of those children:
-
- $rs = $schema->resultset('Person')->search(
- undef,
- {
- alias => 'mother', # alias columns in accordance with "from"
- from => [
- { mother => 'person' },
- [
- [
- { child => 'person' },
- [
- { father => 'person' },
- { 'father.person_id' => 'child.father_id' }
- ]
- ],
- { 'mother.person_id' => 'child.mother_id' }
- ],
- ]
- },
- );
-
- # Equivalent SQL:
- # SELECT mother.* FROM person mother
- # JOIN (
- # person child
- # JOIN person father
- # ON ( father.person_id = child.father_id )
- # )
- # ON ( mother.person_id = child.mother_id )
-
-The type of any join can be controlled manually. To search against only people
-with a father in the person table, we could explicitly use C<INNER JOIN>:
-
- $rs = $schema->resultset('Person')->search(
- undef,
- {
- alias => 'child', # alias columns in accordance with "from"
- from => [
- { child => 'person' },
- [
- { father => 'person', -join_type => 'inner' },
- { 'father.id' => 'child.father_id' }
- ],
- ]
- },
- );
-
- # Equivalent SQL:
- # SELECT child.* FROM person child
- # INNER JOIN person father ON child.father_id = father.id
-
-You can select from a subquery by passing a resultset to from as follows.
-
- $schema->resultset('Artist')->search(
- undef,
- { alias => 'artist2',
- from => [ { artist2 => $artist_rs->as_query } ],
- } );
-
- # and you'll get sql like this..
- # SELECT artist2.artistid, artist2.name, artist2.rank, artist2.charfield FROM
- # ( SELECT me.artistid, me.name, me.rank, me.charfield FROM artists me ) artist2
-
-If you need to express really complex joins, you
-can supply literal SQL to C<from> via a scalar reference. In this case
-the contents of the scalar will replace the table name associated with the
-resultsource.
-
-WARNING: This technique might very well not work as expected on chained
-searches - you have been warned.
-
- # Assuming the Event resultsource is defined as:
-
- MySchema::Event->add_columns (
- sequence => {
- data_type => 'INT',
- is_auto_increment => 1,
- },
- location => {
- data_type => 'INT',
- },
- type => {
- data_type => 'INT',
- },
- );
- MySchema::Event->set_primary_key ('sequence');
-
- # This will get back the latest event for every location. The column
- # selector is still provided by DBIC, all we do is add a JOIN/WHERE
- # combo to limit the resultset
-
- $rs = $schema->resultset('Event');
- $table = $rs->result_source->name;
- $latest = $rs->search (
- undef,
- { from => \ "
- (SELECT e1.* FROM $table e1
- JOIN $table e2
- ON e1.location = e2.location
- AND e1.sequence < e2.sequence
- WHERE e2.sequence is NULL
- ) me",
- },
- );
-
- # Equivalent SQL (with the DBIC chunks added):
-
- SELECT me.sequence, me.location, me.type FROM
- (SELECT e1.* FROM events e1
- JOIN events e2
- ON e1.location = e2.location
- AND e1.sequence < e2.sequence
- WHERE e2.sequence is NULL
- ) me;
-
=head2 for
=over 4