use DBIx::Class::ResultSetColumn;
use base qw/DBIx::Class/;
-__PACKAGE__->load_components(qw/AccessorGroup/);
__PACKAGE__->mk_group_accessors('simple' => qw/result_source result_class/);
=head1 NAME
$attrs = pop(@_) if @_ > 1 and ref $_[$#_] eq 'HASH';
my $our_attrs = { %{$self->{attrs}} };
my $having = delete $our_attrs->{having};
+ my $where = delete $our_attrs->{where};
+
+ 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 = (@_
+
+ my $cond = (@_
? (
(@_ == 1 || ref $_[0] eq "HASH")
- ? shift
+ ? (
+ (ref $_[0] eq 'HASH')
+ ? (
+ (keys %{ $_[0] } > 0)
+ ? shift
+ : undef
+ )
+ : shift
+ )
: (
(@_ % 2)
? $self->throw_exception("Odd number of arguments to search")
: $where);
}
+ if (defined $cond) {
+ $new_attrs->{where} = (
+ defined $new_attrs->{where}
+ ? { '-and' => [
+ map {
+ ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_
+ } $cond, $new_attrs->{where}
+ ]
+ }
+ : $cond);
+ }
+
if (defined $having) {
$new_attrs->{having} = (
defined $new_attrs->{having}
$input_query = {@_};
}
+ my (%related, $info);
+
+ foreach my $key (keys %$input_query) {
+ if (ref($input_query->{$key})
+ && ($info = $self->result_source->relationship_info($key))) {
+ my $rel_q = $self->result_source->resolve_condition(
+ $info->{cond}, delete $input_query->{$key}, $key
+ );
+ die "Can't handle OR join condition in find" if ref($rel_q) eq 'ARRAY';
+ @related{keys %$rel_q} = values %$rel_q;
+ }
+ }
+ if (my @keys = keys %related) {
+ @{$input_query}{@keys} = values %related;
+ }
+
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}
$attrs->{where}, $attrs
);
- return (@data ? $self->_construct_object(@data) : ());
+ return (@data ? ($self->_construct_object(@data))[0] : ());
}
# _is_unique_query
$self->{all_cache_position} = 1;
return ($self->all)[0];
}
+ if ($self->{stashed_objects}) {
+ my $obj = shift(@{$self->{stashed_objects}});
+ delete $self->{stashed_objects} unless @{$self->{stashed_objects}};
+ return $obj;
+ }
my @row = (
exists $self->{stashed_row}
? @{delete $self->{stashed_row}}
: $self->cursor->next
);
return unless (@row);
- return $self->_construct_object(@row);
+ my ($row, @more) = $self->_construct_object(@row);
+ $self->{stashed_objects} = \@more if @more;
+ return $row;
}
sub _construct_object {
my ($self, @row) = @_;
my $info = $self->_collapse_result($self->{_attrs}{as}, \@row);
- my $new = $self->result_class->inflate_result($self->result_source, @$info);
- $new = $self->{_attrs}{record_filter}->($new)
+ my @new = $self->result_class->inflate_result($self->result_source, @$info);
+ @new = $self->{_attrs}{record_filter}->(@new)
if exists $self->{_attrs}{record_filter};
- return $new;
+ return @new;
}
sub _collapse_result {
# appropriately, returning the new condition.
sub _cond_for_update_delete {
- my ($self) = @_;
+ my ($self, $full_cond) = @_;
my $cond = {};
+ $full_cond ||= $self->{cond};
# No-op. No condition, we're updating/deleting everything
- return $cond unless ref $self->{cond};
+ return $cond unless ref $full_cond;
- if (ref $self->{cond} eq 'ARRAY') {
+ if (ref $full_cond eq 'ARRAY') {
$cond = [
map {
my %hash;
$hash{$1} = $_->{$key};
}
\%hash;
- } @{$self->{cond}}
+ } @{$full_cond}
];
}
- elsif (ref $self->{cond} eq 'HASH') {
- if ((keys %{$self->{cond}})[0] eq '-and') {
+ elsif (ref $full_cond eq 'HASH') {
+ if ((keys %{$full_cond})[0] eq '-and') {
$cond->{-and} = [];
- my @cond = @{$self->{cond}{-and}};
+ my @cond = @{$full_cond->{-and}};
for (my $i = 0; $i < @cond; $i++) {
my $entry = $cond[$i];
- my %hash;
+ my $hash;
if (ref $entry eq 'HASH') {
- foreach my $key (keys %{$entry}) {
- $key =~ /([^.]+)$/;
- $hash{$1} = $entry->{$key};
- }
+ $hash = $self->_cond_for_update_delete($entry);
}
else {
$entry =~ /([^.]+)$/;
- $hash{$1} = $cond[++$i];
+ $hash->{$1} = $cond[++$i];
}
- push @{$cond->{-and}}, \%hash;
+ push @{$cond->{-and}}, $hash;
}
}
else {
- foreach my $key (keys %{$self->{cond}}) {
+ foreach my $key (keys %{$full_cond}) {
$key =~ /([^.]+)$/;
- $cond->{$1} = $self->{cond}{$key};
+ $cond->{$1} = $full_cond->{$key};
}
}
}
Deletes the contents of the resultset from its result source. Note that this
will not run DBIC cascade triggers. See L</delete_all> if you need triggers
-to run.
+to run. See also L<DBIx::Class::Row/delete>.
=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 $collapsed_cond = $self->{cond} ? $self->_collapse_cond($self->{cond}) : {};
+ my %new = (
+ %{ $self->_remove_alias($values, $alias) },
+ %{ $self->_remove_alias($collapsed_cond, $alias) },
+ -result_source => $self->result_source,
+ );
+
my $obj = $self->result_class->new(\%new);
- $obj->result_source($self->result_source) if $obj->can('result_source');
return $obj;
}
+# _collapse_cond
+#
+# Recursively collapse the condition.
+
+sub _collapse_cond {
+ my ($self, $cond, $collapsed) = @_;
+
+ $collapsed ||= {};
+
+ if (ref $cond eq 'ARRAY') {
+ foreach my $subcond (@$cond) {
+ next unless ref $subcond; # -or
+# warn "ARRAY: " . Dumper $subcond;
+ $collapsed = $self->_collapse_cond($subcond, $collapsed);
+ }
+ }
+ elsif (ref $cond eq 'HASH') {
+ if (keys %$cond and (keys %$cond)[0] eq '-and') {
+ foreach my $subcond (@{$cond->{-and}}) {
+# warn "HASH: " . Dumper $subcond;
+ $collapsed = $self->_collapse_cond($subcond, $collapsed);
+ }
+ }
+ else {
+# warn "LEAF: " . Dumper $cond;
+ foreach my $col (keys %$cond) {
+ my $value = $cond->{$col};
+ $collapsed->{$col} = $value;
+ }
+ }
+ }
+
+ return $collapsed;
+}
+
+# _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 %orig = %{ $query || {} };
+ my %unaliased;
+
+ foreach my $key (keys %orig) {
+ if ($key !~ /\./) {
+ $unaliased{$key} = $orig{$key};
+ next;
+ }
+ $unaliased{$1} = $orig{$key}
+ if $key =~ m/^(?:\Q$alias\E\.)?([^.]+)$/;
+ }
+
+ return \%unaliased;
+}
+
=head2 find_or_new
=over 4
through directly to SQL, so you can give e.g. C<year DESC> for a
descending order on the column `year'.
-Please note that if you have quoting enabled (see
-L<DBIx::Class::Storage/quote_char>) you will need to do C<\'year DESC' > to
+Please note that if you have C<quote_char> enabled (see
+L<DBIx::Class::Storage::DBI/connect_info>) you will need to do C<\'year DESC' > to
specify an order. (The scalar ref causes it to be passed as raw sql to the DB,
so you will need to manually quote things as appropriate.)