sub search_rs {
my $self = shift;
+ # Special-case handling for (undef, undef).
+ if ( @_ == 2 && !defined $_[1] && !defined $_[0] ) {
+ pop(@_); pop(@_);
+ }
+
my $attrs = {};
$attrs = pop(@_) if @_ > 1 and ref $_[$#_] eq 'HASH';
my $our_attrs = { %{$self->{attrs}} };
my $new_attrs = { %{$our_attrs}, %{$attrs} };
# merge new attrs into inherited
- foreach my $key (qw/join prefetch +select +as/) {
+ foreach my $key (qw/join prefetch +select +as bind/) {
next unless exists $attrs->{$key};
$new_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, $attrs->{$key});
}
resultset query.
CAVEAT: C<search_literal> is provided for Class::DBI compatibility and should
-only be used in that context. There are known problems using C<search_literal>
-in chained queries; it can result in bind values in the wrong order. See
-L<DBIx::Class::Manual::Cookbook/Searching> and
+only be used in that context. C<search_literal> is a convenience method.
+It is equivalent to calling $schema->search(\[]), but if you want to ensure
+columns are bound correctly, use C<search>.
+
+Example of how to use C<search> instead of C<search_literal>
+
+ my @cds = $cd_rs->search_literal('cdid = ? AND (artist = ? OR artist = ?)', (2, 1, 2));
+ my @cds = $cd_rs->search(\[ 'cdid = ? AND (artist = ? OR artist = ?)', [ 'cdid', 2 ], [ 'artist', 1 ], [ 'artist', 2 ] ]);
+
+
+See L<DBIx::Class::Manual::Cookbook/Searching> and
L<DBIx::Class::Manual::FAQ/Searching> for searching techniques that do not
require C<search_literal>.
=cut
sub search_literal {
- my ($self, $cond, @vals) = @_;
- my $attrs = (ref $vals[$#vals] eq 'HASH' ? { %{ pop(@vals) } } : {});
- $attrs->{bind} = [ @{$self->{attrs}{bind}||[]}, @vals ];
- return $self->search(\$cond, $attrs);
+ my ($self, $sql, @bind) = @_;
+ my $attr;
+ if ( @bind && ref($bind[-1]) eq 'HASH' ) {
+ $attr = pop @bind;
+ }
+ return $self->search(\[ $sql, map [ __DUMMY__ => $_ ], @bind ], ($attr || () ));
}
=head2 find
if (ref $query eq 'ARRAY') {
foreach my $subquery (@$query) {
next unless ref $subquery; # -or
-# warn "ARRAY: " . Dumper $subquery;
$collapsed = $self->_collapse_query($subquery, $collapsed);
}
}
elsif (ref $query eq 'HASH') {
if (keys %$query and (keys %$query)[0] eq '-and') {
foreach my $subquery (@{$query->{-and}}) {
-# warn "HASH: " . Dumper $subquery;
$collapsed = $self->_collapse_query($subquery, $collapsed);
}
}
else {
-# warn "LEAF: " . Dumper $query;
foreach my $col (keys %$query) {
my $value = $query->{$col};
$collapsed->{$col}{$value}++;
For more information, see L<DBIx::Class::Manual::Cookbook>.
+This method is deprecated and will be removed in 0.09. Use L</search()>
+instead. An example conversion is:
+
+ ->search_like({ foo => 'bar' });
+
+ # Becomes
+
+ ->search({ foo => { like => 'bar' } });
+
=cut
sub search_like {
my $class = shift;
+ carp join ("\n",
+ 'search_like() is deprecated and will be removed in 0.09.',
+ 'Instead use ->search({ x => { -like => "y%" } })',
+ '(note the outer pair of {}s - they are important!)'
+ );
my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
my $query = ref $_[0] eq 'HASH' ? { %{shift()} }: {@_};
$query->{$_} = { 'like' => $query->{$_} } for keys %$query;
C<< result_source->result_class >> - which in most cases is the name of the
L<"table"|DBIx::Class::Manual::Glossary/"ResultSource"> class.
+Note that changing the result_class will also remove any components
+that were originally loaded in the source class via
+L<DBIx::Class::ResultSource/load_components>. Any overloaded methods
+in the original source class will not run.
+
=cut
sub result_class {
with to find the number of elements. If passed arguments, does a search
on the resultset and counts the results of that.
-Note: When using C<count> with C<group_by>, L<DBIx::Class> emulates C<GROUP BY>
-using C<COUNT( DISTINCT( columns ) )>. Some databases (notably SQLite) do
-not support C<DISTINCT> with multiple columns. If you are using such a
-database, you should only use columns from the main table in your C<group_by>
-clause.
-
=cut
sub count {
sub _count { # Separated out so pager can get the full count
my $self = shift;
- my $select = { count => '*' };
-
my $attrs = { %{$self->_resolved_attrs} };
- if (my $group_by = delete $attrs->{group_by}) {
- delete $attrs->{having};
- my @distinct = (ref $group_by ? @$group_by : ($group_by));
- # todo: try CONCAT for multi-column pk
- my @pk = $self->result_source->primary_columns;
- if (@pk == 1) {
- my $alias = $attrs->{alias};
- foreach my $column (@distinct) {
- if ($column =~ qr/^(?:\Q${alias}.\E)?$pk[0]$/) {
- @distinct = ($column);
- last;
- }
- }
- }
- $select = { count => { distinct => \@distinct } };
+ if (my $group_by = $attrs->{group_by}) {
+ delete $attrs->{order_by};
+
+ $attrs->{select} = $group_by;
+ $attrs->{from} = [ { 'mesub' => (ref $self)->new($self->result_source, $attrs)->cursor->as_query } ];
+ delete $attrs->{where};
}
- $attrs->{select} = $select;
+ $attrs->{select} = { count => '*' };
$attrs->{as} = [qw/count/];
- # offset, order by and page are not needed to count. record_filter is cdbi
- delete $attrs->{$_} for qw/rows offset order_by page pager record_filter/;
+ # offset, order by, group by, where and page are not needed to count. record_filter is cdbi
+ delete $attrs->{$_} for qw/rows offset order_by group_by page pager record_filter/;
my $tmp_rs = (ref $self)->new($self->result_source, $attrs);
my ($count) = $tmp_rs->cursor->next;
# No-op. No condition, we're updating/deleting everything
return $cond unless ref $full_cond;
+ # Some attributes when present require a subquery
+ # This might not work on some database (mysql), but...
+ # it won't work without the subquery either so who cares
+ if (grep { defined $self->{attrs}{$_} } qw/join seen_join from rows group_by/) {
+
+ foreach my $pk ($self->result_source->primary_columns) {
+ $cond->{$pk} = { IN => $self->get_column($pk)->as_query };
+ }
+
+ return $cond;
+ }
+
if (ref $full_cond eq 'ARRAY') {
$cond = [
map {
elsif (ref $full_cond eq 'HASH') {
if ((keys %{$full_cond})[0] eq '-and') {
$cond->{-and} = [];
-
my @cond = @{$full_cond->{-and}};
- for (my $i = 0; $i < @cond; $i++) {
+ for (my $i = 0; $i < @cond; $i++) {
my $entry = $cond[$i];
-
my $hash;
if (ref $entry eq 'HASH') {
$hash = $self->_cond_for_update_delete($entry);
$entry =~ /([^.]+)$/;
$hash->{$1} = $cond[++$i];
}
-
push @{$cond->{-and}}, $hash;
}
}
}
}
else {
- $self->throw_exception(
- "Can't update/delete on resultset with condition unless hash or array"
- );
+ $self->throw_exception("Can't update/delete on resultset with condition unless hash or array");
}
-
+
return $cond;
}
$self->throw_exception("Values for update must be a hash")
unless ref $values eq 'HASH';
- carp( 'WARNING! Currently $rs->update() does not generate proper SQL'
- . ' on joined resultsets, and may affect rows well outside of the'
- . ' contents of $rs. Use at your own risk' )
- if ( $self->{attrs}{seen_join} );
-
my $cond = $self->_cond_for_update_delete;
-
+
return $self->result_source->storage->update(
$self->result_source, $values, $cond
);
delete may not generate correct SQL for a query with joins or a resultset
chained from a related resultset. In this case it will generate a warning:-
- WARNING! Currently $rs->delete() does not generate proper SQL on
- joined resultsets, and may delete rows well outside of the contents
- of $rs. Use at your own risk
-
In these cases you may find that delete_all is more appropriate, or you
need to respecify your query in a way that can be expressed without a join.
my ($self) = @_;
$self->throw_exception("Delete should not be passed any arguments")
if $_[1];
- carp( 'WARNING! Currently $rs->delete() does not generate proper SQL'
- . ' on joined resultsets, and may delete rows well outside of the'
- . ' contents of $rs. Use at your own risk' )
- if ( $self->{attrs}{seen_join} );
+
my $cond = $self->_cond_for_update_delete;
$self->result_source->storage->delete($self->result_source, $cond);
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 $self->create($cond);
}
+=head2 update_or_new
+
+=over 4
+
+=item Arguments: \%col_values, { key => $unique_constraint }?
+
+=item Return Value: $rowobject
+
+=back
+
+ $resultset->update_or_new({ col => $val, ... });
+
+First, searches for an existing row matching one of the unique constraints
+(including the primary key) on the source of this resultset. If a row is
+found, updates it with the other given column values. Otherwise, instantiate
+a new result object and return it. The object will not be saved into your storage
+until you call L<DBIx::Class::Row/insert> on it.
+
+Takes an optional C<key> attribute to search on a specific unique constraint.
+For example:
+
+ # In your application
+ my $cd = $schema->resultset('CD')->update_or_new(
+ {
+ artist => 'Massive Attack',
+ title => 'Mezzanine',
+ year => 1998,
+ },
+ { key => 'cd_artist_title' }
+ );
+
+ if ($cd->in_storage) {
+ # the cd was updated
+ }
+ else {
+ # the cd is not yet in the database, let's insert it
+ $cd->insert;
+ }
+
+See also L</find>, L</find_or_create> and L<find_or_new>.
+
+=cut
+
+sub update_or_new {
+ my $self = shift;
+ my $attrs = ( @_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {} );
+ my $cond = ref $_[0] eq 'HASH' ? shift : {@_};
+
+ my $row = $self->find( $cond, $attrs );
+ if ( defined $row ) {
+ $row->update($cond);
+ return $row;
+ }
+
+ return $self->new_result($cond);
+}
+
=head2 get_cache
=over 4
# 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($_) eq 'HASH' )
+ ? $_
+ : {
+ (
+ /^\Q${alias}.\E(.+)$/
+ ? "$1"
+ : "$_"
+ )
+ =>
+ (
+ /\./
+ ? "$_"
+ : "${alias}.$_"
+ )
}
} ( ref($attrs->{columns}) eq 'ARRAY' ) ? @{ delete $attrs->{columns}} : (delete $attrs->{columns} || $source->columns );
}