$attrs->{alias} ||= 'me';
- bless {
+ my $self = {
result_source => $source,
result_class => $attrs->{result_class} || $source->result_class,
cond => $attrs->{where},
count => undef,
pager => undef,
attrs => $attrs
- }, $class;
+ };
+
+ bless $self, $class;
+
+ return $self;
}
=head2 search
columns => [qw/name artistid/],
});
+For a list of attributes that can be passed to C<search>, see L</ATTRIBUTES>. For more examples of using this function, see L<Searching|DBIx::Class::Manual::Cookbook/Searching>.
+
=cut
sub search {
my $our_attrs = { %{$self->{attrs}} };
my $having = delete $our_attrs->{having};
+ my $new_attrs = { %{$our_attrs}, %{$attrs} };
+
# merge new attrs into inherited
foreach my $key (qw/join prefetch/) {
next unless exists $attrs->{$key};
- $our_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, delete $attrs->{$key});
+ $new_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, $attrs->{$key});
}
- my $new_attrs = { %{$our_attrs}, %{$attrs} };
my $where = (@_
? (
(@_ == 1 || ref $_[0] eq "HASH")
If no C<key> is specified, it searches on all unique constraints defined on the
source, including the primary key.
+If your table does not have a primary key, you B<must> provide a value for the
+C<key> attribute matching one of the unique constraints on the source.
+
See also L</find_or_create> and L</update_or_create>. For information on how to
declare unique constraints, see
L<DBIx::Class::ResultSource/add_unique_constraint>.
? $self->result_source->unique_constraint_columns($attrs->{key})
: $self->result_source->primary_columns;
$self->throw_exception(
- "Can't find unless a primary key or unique constraint is defined"
+ "Can't find unless a primary key is defined or unique constraint is specified"
) unless @cols;
# Parse out a hashref from input
my @unique_queries = $self->_unique_queries($input_query, $attrs);
- # Handle cases where the ResultSet defines the query, or where the user is
- # abusing find
- my $query = @unique_queries ? \@unique_queries : $input_query;
+ # Build the final query: Default to the disjunction of the unique queries,
+ # but allow the input query in case the ResultSet defines the query or the
+ # user is abusing find
+ my $alias = exists $attrs->{alias} ? $attrs->{alias} : $self->{attrs}{alias};
+ my $query = @unique_queries
+ ? [ map { $self->_add_alias($_, $alias) } @unique_queries ]
+ : $self->_add_alias($input_query, $alias);
# Run the query
if (keys %$attrs) {
}
}
+# _add_alias
+#
+# Add the specified alias to the specified query hash. A copy is made so the
+# original query is not modified.
+
+sub _add_alias {
+ my ($self, $query, $alias) = @_;
+
+ my %aliased = %$query;
+ foreach my $col (grep { ! m/\./ } keys %aliased) {
+ $aliased{"$alias.$col"} = delete $aliased{$col};
+ }
+
+ return \%aliased;
+}
+
# _unique_queries
#
# Build a list of queries which satisfy unique constraints.
sub _unique_queries {
my ($self, $query, $attrs) = @_;
- my $alias = $self->{attrs}{alias};
my @constraint_names = exists $attrs->{key}
? ($attrs->{key})
: $self->result_source->unique_constraint_names;
my $num_query = scalar keys %$unique_query;
next unless $num_query;
- # Add the ResultSet's alias
- foreach my $col (grep { ! m/\./ } keys %$unique_query) {
- $unique_query->{"$alias.$col"} = delete $unique_query->{$col};
- }
-
# XXX: Assuming quite a bit about $self->{attrs}{where}
my $num_cols = scalar @unique_cols;
my $num_where = exists $self->{attrs}{where}
}
}
- unless ($self->_is_unique_query($attrs->{where})) {
- carp "Query not guaranteed to return a single row"
- . "; please declare your unique constraints or use search instead";
- }
+# XXX: Disabled since it doesn't infer uniqueness in all cases
+# unless ($self->_is_unique_query($attrs->{where})) {
+# carp "Query not guaranteed to return a single row"
+# . "; please declare your unique constraints or use search instead";
+# }
my @data = $self->result_source->storage->select_single(
$attrs->{from}, $attrs->{select},
my $max_length = $rs->get_column('length')->max;
-Returns a ResultSetColumn instance for $column based on $self
+Returns a L<DBIx::Class::ResultSetColumn> instance for a column of the ResultSet.
=cut
An accessor for the primary ResultSource object from which this ResultSet
is derived.
+=head2 result_class
+
+=over 4
+
+=item Arguments: $result_class?
+
+=item Return Value: $result_class
+
+=back
+
+An accessor for the class to use when creating row objects. Defaults to
+C<< result_source->result_class >> - which in most cases is the name of the
+L<"table"|DBIx::Class::Manual::Glossary/"ResultSource"> class.
+
=cut
$self->throw_exception(
"Can't abstract implicit construct, condition not a hash"
) if ($self->{cond} && !(ref $self->{cond} eq 'HASH'));
- my %new = %$values;
+
my $alias = $self->{attrs}{alias};
- foreach my $key (keys %{$self->{cond}||{}}) {
- $new{$1} = $self->{cond}{$key} if ($key =~ m/^(?:\Q${alias}.\E)?([^.]+)$/);
- }
+ my %new = (
+ %{ $self->_remove_alias($values, $alias) },
+ %{ $self->_remove_alias($self->{cond}, $alias) },
+ );
+
my $obj = $self->result_class->new(\%new);
$obj->result_source($self->result_source) if $obj->can('result_source');
return $obj;
}
+# _remove_alias
+#
+# Remove the specified alias from the specified query hash. A copy is made so
+# the original query is not modified.
+
+sub _remove_alias {
+ my ($self, $query, $alias) = @_;
+
+ my %unaliased = %{ $query || {} };
+ foreach my $key (keys %unaliased) {
+ $unaliased{$1} = delete $unaliased{$key}
+ if $key =~ m/^(?:\Q$alias\E\.)?([^.]+)$/;
+ }
+
+ return \%unaliased;
+}
+
=head2 find_or_new
=over 4
my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
my $cond = ref $_[0] eq 'HASH' ? shift : {@_};
- my $row = $self->find($cond);
+ my $row = $self->find($cond, $attrs);
if (defined $row) {
$row->update($cond);
return $row;
alias => $alias,
where => $self->{cond},
seen_join => $seen,
- _parent_from => $from,
+ from => $from,
});
};
}
my $source = $self->result_source;
my $attrs = $self->{attrs};
- my $from = $attrs->{_parent_from}
+ my $from = $attrs->{from}
|| [ { $attrs->{alias} => $source->from } ];
my $seen = { %{$attrs->{seen_join}||{}} };
my $join = ($attrs->{join}
? [ $attrs->{join}, $extra_join ]
: $extra_join);
- push(@{$from},
- $source->resolve_join($join, $attrs->{alias}, $seen)
- );
+ $from = [
+ @$from,
+ ($join ? $source->resolve_join($join, $attrs->{alias}, $seen) : ()),
+ ];
return ($from,$seen);
}
} elsif (!$attrs->{select}) {
$attrs->{columns} = [ $source->columns ];
}
-
- $attrs->{select} ||= [
- map { m/\./ ? $_ : "${alias}.$_" } @{delete $attrs->{columns}}
- ];
- $attrs->{as} ||= [
- map { m/^\Q${alias}.\E(.+)$/ ? $1 : $_ } @{$attrs->{select}}
- ];
+
+ $attrs->{select} =
+ ($attrs->{select}
+ ? (ref $attrs->{select} eq 'ARRAY'
+ ? [ @{$attrs->{select}} ]
+ : [ $attrs->{select} ])
+ : [ map { m/\./ ? $_ : "${alias}.$_" } @{delete $attrs->{columns}} ]
+ );
+ $attrs->{as} =
+ ($attrs->{as}
+ ? (ref $attrs->{as} eq 'ARRAY'
+ ? [ @{$attrs->{as}} ]
+ : [ $attrs->{as} ])
+ : [ map { m/^\Q${alias}.\E(.+)$/ ? $1 : $_ } @{$attrs->{select}} ]
+ );
my $adds;
if ($adds = delete $attrs->{include_columns}) {
}
if ($adds = delete $attrs->{'+select'}) {
$adds = [$adds] unless ref $adds eq 'ARRAY';
- push(@{$attrs->{select}}, map { /\./ || ref $_ ? $_ : "${alias}.$_" } @$adds);
+ push(@{$attrs->{select}},
+ map { /\./ || ref $_ ? $_ : "${alias}.$_" } @$adds);
}
if (my $adds = delete $attrs->{'+as'}) {
$adds = [$adds] unless ref $adds eq 'ARRAY';
push(@{$attrs->{as}}, @$adds);
}
- $attrs->{from} ||= delete $attrs->{_parent_from}
- || [ { 'me' => $source->from } ];
+ $attrs->{from} ||= [ { 'me' => $source->from } ];
if (exists $attrs->{join} || exists $attrs->{prefetch}) {
my $join = delete $attrs->{join} || {};
);
}
- push(@{$attrs->{from}},
- $source->resolve_join($join, $alias, { %{$attrs->{seen_join}||{}} })
- );
+ $attrs->{from} = # have to copy here to avoid corrupting the original
+ [
+ @{$attrs->{from}},
+ $source->resolve_join($join, $alias, { %{$attrs->{seen_join}||{}} })
+ ];
}
$attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct};
if ($attrs->{order_by}) {
- $attrs->{order_by} = [ $attrs->{order_by} ] unless ref $attrs->{order_by};
+ $attrs->{order_by} = (ref($attrs->{order_by}) eq 'ARRAY'
+ ? [ @{$attrs->{order_by}} ]
+ : [ $attrs->{order_by} ]);
} else {
- $attrs->{order_by} ||= [];
+ $attrs->{order_by} = [];
}
my $collapse = $attrs->{collapse} || {};
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 @prefetch = $source->resolve_prefetch(
- $p, $alias, { %{$attrs->{seen_join}||{}} }, \@pre_order, $collapse
+ $p, $alias, $seen, \@pre_order, $collapse
);
push(@{$attrs->{select}}, map { $_->[0] } @prefetch);
push(@{$attrs->{as}}, map { $_->[1] } @prefetch);
sub _merge_attr {
my ($self, $a, $b) = @_;
- return $b unless $a;
+ return $b unless defined($a);
+ return $a unless defined($b);
if (ref $b eq 'HASH' && ref $a eq 'HASH') {
foreach my $key (keys %{$b}) {
You can create your own accessors if required - see
L<DBIx::Class::Manual::Cookbook> for details.
-Please note: This will NOT insert an C<AS employee_count> into the SQL statement
-produced, it is used for internal access only. Thus attempting to use the accessor
-in an C<order_by> clause or similar will fail misrably.
+Please note: This will NOT insert an C<AS employee_count> into the SQL
+statement produced, it is used for internal access only. Thus
+attempting to use the accessor in an C<order_by> clause or similar
+will fail miserably.
+
+To get around this limitation, you can supply literal SQL to your
+C<select> attibute that contains the C<AS alias> text, eg:
+
+ select => [\'myfield AS alias']
=head2 join
}
);
+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:
+
+ # Get CDs from 1984 with a 'Foo' track
+ my $rs = $schema->resultset('CD')->search(
+ {
+ 'me.year' => 1984,
+ 'tracks.name' => 'Foo'
+ },
+ { join => 'tracks' }
+ );
+
If the same join is supplied twice, it will be aliased to <rel>_2 (and
similarly for a third time). For e.g.