use DBIx::Class::Carp;
use DBIx::Class::ResultSetColumn;
use Scalar::Util qw/blessed weaken reftype/;
-use DBIx::Class::_Util 'fail_on_internal_wantarray';
+use DBIx::Class::_Util qw(
+ fail_on_internal_wantarray is_plain_value is_literal_value
+);
use Try::Tiny;
use Data::Compare (); # no imports!!! guard against insane architecture
you want to check if a resultset has any results, you must use C<if $rs
!= 0>.
-=head1 CUSTOM ResultSet CLASSES THAT USE Moose
-
-If you want to make your custom ResultSet classes with L<Moose>, use a template
-similar to:
-
- package MyApp::Schema::ResultSet::User;
-
- use Moose;
- use namespace::autoclean;
- use MooseX::NonMoose;
- extends 'DBIx::Class::ResultSet';
-
- sub BUILDARGS { $_[2] }
-
- ...your code...
-
- __PACKAGE__->meta->make_immutable;
-
- 1;
-
-The L<MooseX::NonMoose> is necessary so that the L<Moose> constructor does not
-clash with the regular ResultSet constructor. Alternatively, you can use:
-
- __PACKAGE__->meta->make_immutable(inline_constructor => 0);
-
-The L<BUILDARGS|Moose::Manual::Construction/BUILDARGS> is necessary because the
-signature of the ResultSet C<new> is C<< ->new($source, \%args) >>.
-
=head1 EXAMPLES
=head2 Chaining resultsets
See: L</search>, L</count>, L</get_column>, L</all>, L</create>.
+=head2 Custom ResultSet classes
+
+To add methods to your resultsets, you can subclass L<DBIx::Class::ResultSet>, similar to:
+
+ package MyApp::Schema::ResultSet::User;
+
+ use strict;
+ use warnings;
+
+ use base 'DBIx::Class::ResultSet';
+
+ sub active {
+ my $self = shift;
+ $self->search({ $self->current_source_alias . '.active' => 1 });
+ }
+
+ sub unverified {
+ my $self = shift;
+ $self->search({ $self->current_source_alias . '.verified' => 0 });
+ }
+
+ sub created_n_days_ago {
+ my ($self, $days_ago) = @_;
+ $self->search({
+ $self->current_source_alias . '.create_date' => {
+ '<=',
+ $self->result_source->schema->storage->datetime_parser->format_datetime(
+ DateTime->now( time_zone => 'UTC' )->subtract( days => $days_ago )
+ )}
+ });
+ }
+
+ sub users_to_warn { shift->active->unverified->created_n_days_ago(7) }
+
+ 1;
+
+See L<DBIx::Class::Schema/load_namespaces> on how DBIC can discover and
+automatically attach L<Result|DBIx::Class::Manual::ResultClass>-specific
+L<ResulSet|DBIx::Class::ResultSet> classes.
+
+=head3 ResultSet subclassing with Moose and similar constructor-providers
+
+Using L<Moose> or L<Moo> in your ResultSet classes is usually overkill, but
+you may find it useful if your ResultSets contain a lot of business logic
+(e.g. C<has xml_parser>, C<has json>, etc) or if you just prefer to organize
+your code via roles.
+
+In order to write custom ResultSet classes with L<Moo> you need to use the
+following template. The L<BUILDARGS|Moo/BUILDARGS> is necessary due to the
+unusual signature of the L<constructor provided by DBIC
+|DBIx::Class::ResultSet/new> C<< ->new($source, \%args) >>.
+
+ use Moo;
+ extends 'DBIx::Class::ResultSet';
+ sub BUILDARGS { $_[2] } # ::RS::new() expects my ($class, $rsrc, $args) = @_
+
+ ...your code...
+
+ 1;
+
+If you want to build your custom ResultSet classes with L<Moose>, you need
+a similar, though a little more elaborate template in order to interface the
+inlining of the L<Moose>-provided
+L<object constructor|Moose::Manual::Construction/WHERE'S THE CONSTRUCTOR?>,
+with the DBIC one.
+
+ package MyApp::Schema::ResultSet::User;
+
+ use Moose;
+ use MooseX::NonMoose;
+ extends 'DBIx::Class::ResultSet';
+
+ sub BUILDARGS { $_[2] } # ::RS::new() expects my ($class, $rsrc, $args) = @_
+
+ ...your code...
+
+ __PACKAGE__->meta->make_immutable;
+
+ 1;
+
+The L<MooseX::NonMoose> is necessary so that the L<Moose> constructor does not
+entirely overwrite the DBIC one (in contrast L<Moo> does this automatically).
+Alternatively, you can skip L<MooseX::NonMoose> and get by with just L<Moose>
+instead by doing:
+
+ __PACKAGE__->meta->make_immutable(inline_constructor => 0);
+
=head1 METHODS
=head2 new
if $source->isa('DBIx::Class::ResultSourceHandle');
$attrs = { %{$attrs||{}} };
- delete @{$attrs}{qw(_sqlmaker_select_args _related_results_construction)};
+ delete @{$attrs}{qw(_last_sqlmaker_alias_map _related_results_construction)};
if ($attrs->{page}) {
$attrs->{rows} ||= 10;
sub _stack_cond {
my ($self, $left, $right) = @_;
- # collapse single element top-level conditions
- # (single pass only, unlikely to need recursion)
- for ($left, $right) {
- if (ref $_ eq 'ARRAY') {
- if (@$_ == 0) {
- $_ = undef;
- }
- elsif (@$_ == 1) {
- $_ = $_->[0];
- }
- }
- elsif (ref $_ eq 'HASH') {
- my ($first, $more) = keys %$_;
+ (
+ (ref $_ eq 'ARRAY' and !@$_)
+ or
+ (ref $_ eq 'HASH' and ! keys %$_)
+ ) and $_ = undef for ($left, $right);
- # empty hash
- if (! defined $first) {
- $_ = undef;
- }
- # one element hash
- elsif (! defined $more) {
- if ($first eq '-and' and ref $_->{'-and'} eq 'HASH') {
- $_ = $_->{'-and'};
- }
- elsif ($first eq '-or' and ref $_->{'-or'} eq 'ARRAY') {
- $_ = $_->{'-or'};
- }
- }
- }
+ # either on of the two undef or both undef
+ if ( ( (defined $left) xor (defined $right) ) or ! defined $left ) {
+ return defined $left ? $left : $right;
}
- # merge hashes with weeding out of duplicates (simple cases only)
- if (ref $left eq 'HASH' and ref $right eq 'HASH') {
+ my $cond = $self->result_source->schema->storage->_collapse_cond({ -and => [$left, $right] });
- # shallow copy to destroy
- $right = { %$right };
- for (grep { exists $right->{$_} } keys %$left) {
- # the use of eq_deeply here is justified - the rhs of an
- # expression can contain a lot of twisted weird stuff
- delete $right->{$_} if Data::Compare::Compare( $left->{$_}, $right->{$_} );
- }
+ for my $c (grep { ref $cond->{$_} eq 'ARRAY' and ($cond->{$_}[0]||'') eq '-and' } keys %$cond) {
- $right = undef unless keys %$right;
- }
+ my @vals = sort @{$cond->{$c}}[ 1..$#{$cond->{$c}} ];
+ my @fin = shift @vals;
+ for my $v (@vals) {
+ push @fin, $v unless Data::Compare::Compare( $fin[-1], $v );
+ }
- if (defined $left xor defined $right) {
- return defined $left ? $left : $right;
- }
- elsif (! defined $left) {
- return undef;
- }
- else {
- return { -and => [ $left, $right ] };
+ $cond->{$c} = (@fin == 1) ? $fin[0] : [-and => @fin ];
}
+
+ $cond;
}
=head2 search_literal
next if $keyref eq 'ARRAY'; # has_many for multi_create
- my $rel_q = $rsrc->_resolve_condition(
+ my ($rel_cond, $crosstable) = $rsrc->_resolve_condition(
$relinfo->{cond}, $val, $key, $key
);
- die "Can't handle complex relationship conditions in find" if ref($rel_q) ne 'HASH';
- @related{keys %$rel_q} = values %$rel_q;
+
+ $self->throw_exception("Complex condition via relationship '$key' is unsupported in find()")
+ if $crosstable or ref($rel_cond) ne 'HASH';
+
+ # supplement
+ @related{keys %$rel_cond} = values %$rel_cond;
}
}
$self->_construct_results->[0];
}
-
-# _collapse_query
-#
-# Recursively collapse the query, accumulating values for each column.
-
-sub _collapse_query {
- my ($self, $query, $collapsed) = @_;
-
- $collapsed ||= {};
-
- if (ref $query eq 'ARRAY') {
- foreach my $subquery (@$query) {
- next unless ref $subquery; # -or
- $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}}) {
- $collapsed = $self->_collapse_query($subquery, $collapsed);
- }
- }
- else {
- foreach my $col (keys %$query) {
- my $value = $query->{$col};
- $collapsed->{$col}{$value}++;
- }
- }
- }
-
- return $collapsed;
-}
-
=head2 get_column
=over 4
and
$rsrc->schema
->storage
- ->_main_source_order_by_portion_is_stable($rsrc, $attrs->{order_by}, $attrs->{where})
+ ->_extract_colinfo_of_stable_main_source_order_by_portion($rsrc, $attrs->{order_by}, $attrs->{where})
) ? 1 : 0
) unless defined $attrs->{_ordered_for_collapse};
return undef unless @{$rows||[]};
# sanity check - people are too clever for their own good
- if ($attrs->{collapse} and my $aliastypes = $attrs->{_sqlmaker_select_args}[3]{_aliastypes} ) {
+ if ($attrs->{collapse} and my $aliastypes = $attrs->{_last_sqlmaker_alias_map} ) {
my $multiplied_selectors;
for my $sel_alias ( grep { $_ ne $attrs->{alias} } keys %{ $aliastypes->{selecting} } ) {
$guard = $storage->txn_scope_guard;
- $cond = [];
for my $row ($subrs->cursor->all) {
push @$cond, { map
{ $idcols->[$_] => $row->[$_] }
}
}
- my $res = $storage->$op (
+ my $res = $cond ? $storage->$op (
$rsrc,
$op eq 'update' ? $values : (),
$cond,
- );
+ ) : '0E0';
$guard->commit if $guard;
foreach my $rel (@rels) {
next unless ref $data->[$index]->{$rel} eq "HASH";
my $result = $self->related_resultset($rel)->create($data->[$index]->{$rel});
- my ($reverse_relname, $reverse_relinfo) = %{$rsrc->reverse_relationship_info($rel)};
+ my (undef, $reverse_relinfo) = %{$rsrc->reverse_relationship_info($rel)};
my $related = $result->result_source->_resolve_condition(
$reverse_relinfo->{cond},
$self,
);
}
else {
- # precedence must be given to passed values over values inherited from
- # the cond, so the order here is important.
- my $collapsed_cond = $self->_collapse_cond($self->{cond});
- my %implied = %{$self->_remove_alias($collapsed_cond, $alias)};
+ if ($self->{cond}) {
+ my $implied = $self->_remove_alias(
+ $self->result_source->schema->storage->_collapse_cond($self->{cond}),
+ $alias,
+ );
- while ( my($col, $value) = each %implied ) {
- my $vref = ref $value;
- if (
- $vref eq 'HASH'
- and
- keys(%$value) == 1
- and
- (keys %$value)[0] eq '='
- ) {
- $new_data{$col} = $value->{'='};
- }
- elsif( !$vref or $vref eq 'SCALAR' or blessed($value) ) {
- $new_data{$col} = $value;
+ for my $c (keys %$implied) {
+ my $v = $implied->{$c};
+ if ( ! length ref $v or is_plain_value($v) ) {
+ $new_data{$c} = $v;
+ }
+ elsif (
+ ref $v eq 'HASH' and keys %$v == 1 and exists $v->{'='} and is_literal_value($v->{'='})
+ ) {
+ $new_data{$c} = $v->{'='};
+ }
}
}
}
+ # precedence must be given to passed values over values inherited from
+ # the cond, so the order here is important.
%new_data = (
%new_data,
%{ $self->_remove_alias($data, $alias) },
return 0;
}
-# _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
- $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}}) {
- $collapsed = $self->_collapse_cond($subcond, $collapsed);
- }
- }
- else {
- 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
$attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
);
- $self->{_attrs}{_sqlmaker_select_args} = $attrs->{_sqlmaker_select_args};
-
$aq;
}
if (my $cache = $self->get_cache) {
my @related_cache = map
- { @{$_->related_resultset($rel)->get_cache||[]} }
+ { $_->related_resultset($rel)->get_cache || () }
@$cache
;
- $new->set_cache(\@related_cache) if @related_cache;
+ $new->set_cache([ map @$_, @related_cache ]) if @related_cache == @$cache;
}
$new;
expression). Adds C<me.> onto the start of any column without a C<.> in
it and sets C<select> from that, then auto-populates C<as> from
C<select> as normal. (You may also use the C<cols> attribute, as in
-earlier versions of DBIC, but this is deprecated.)
+earlier versions of DBIC, but this is deprecated)
Essentially C<columns> does the same as L</select> and L</as>.
- columns => [ 'foo', { bar => 'baz' } ]
+ columns => [ 'some_column', { dbic_slot => 'another_column' } ]
is the same as
- select => [qw/foo baz/],
- as => [qw/foo bar/]
+ select => [qw(some_column another_column)],
+ as => [qw(some_column dbic_slot)]
+
+If you want to individually retrieve related columns (in essence perform
+manual prefetch) you have to make sure to specify the correct inflation slot
+chain such that it matches existing relationships:
+
+ my $rs = $schema->resultset('Artist')->search({}, {
+ # required to tell DBIC to collapse has_many relationships
+ collapse => 1,
+ join => { cds => 'tracks'},
+ '+columns' => {
+ 'cds.cdid' => 'cds.cdid',
+ 'cds.tracks.title' => 'tracks.title',
+ },
+ });
=head2 +columns
+B<NOTE:> You B<MUST> explicitly quote C<'+columns'> when using this attribute.
+Not doing so causes Perl to incorrectly interpret C<+columns> as a bareword
+with a unary plus operator before it, which is the same as simply C<columns>.
+
=over 4
-=item Value: \@columns
+=item Value: \@extra_columns
=back
Indicates additional columns to be selected from storage. Works the same as
-L</columns> but adds columns to the selection. (You may also use the
+L</columns> but adds columns to the current selection. (You may also use the
C<include_columns> attribute, as in earlier versions of DBIC, but this is
-deprecated). For example:-
+deprecated)
$schema->resultset('CD')->search(undef, {
'+columns' => ['artist.name'],
column (or relationship) accessor, and 'name' is the name of the column
accessor in the related table.
-B<NOTE:> You need to explicitly quote '+columns' when defining the attribute.
-Not doing so causes Perl to incorrectly interpret +columns as a bareword with a
-unary plus operator before it.
-
-=head2 include_columns
-
-=over 4
-
-=item Value: \@columns
-
-=back
-
-Deprecated. Acts as a synonym for L</+columns> for backward compatibility.
-
=head2 select
=over 4
e.g. an C<ORDER BY> clause. This is done via the C<-as> B<select function
attribute> supplied as shown in the example above.
-B<NOTE:> You need to explicitly quote '+select'/'+as' when defining the attributes.
-Not doing so causes Perl to incorrectly interpret them as a bareword with a
-unary plus operator before it.
-
=head2 +select
+B<NOTE:> You B<MUST> explicitly quote C<'+select'> when using this attribute.
+Not doing so causes Perl to incorrectly interpret C<+select> as a bareword
+with a unary plus operator before it, which is the same as simply C<select>.
+
=over 4
-Indicates additional columns to be selected from storage. Works the same as
-L</select> but adds columns to the default selection, instead of specifying
-an explicit list.
+=item Value: \@extra_select_columns
=back
+Indicates additional columns to be selected from storage. Works the same as
+L</select> but adds columns to the current selection, instead of specifying
+a new explicit list.
+
=head2 as
=over 4
=back
-Indicates column names for object inflation. That is L</as> indicates the
+Indicates DBIC-side names for object inflation. That is L</as> indicates the
slot name in which the column value will be stored within the
L<Row|DBIx::Class::Row> object. The value will then be accessible via this
identifier by the C<get_column> method (or via the object accessor B<if one
=head2 +as
+B<NOTE:> You B<MUST> explicitly quote C<'+as'> when using this attribute.
+Not doing so causes Perl to incorrectly interpret C<+as> as a bareword
+with a unary plus operator before it, which is the same as simply C<as>.
+
=over 4
-Indicates additional column names for those added via L</+select>. See L</as>.
+=item Value: \@extra_inflation_names
=back
+Indicates additional inflation names for selectors added via L</+select>. See L</as>.
+
=head2 join
=over 4