use base qw/DBIx::Class/;
__PACKAGE__->load_components(qw/AccessorGroup/);
-__PACKAGE__->mk_group_accessors('simple' => 'result_source');
+__PACKAGE__->mk_group_accessors('simple' => qw/result_source result_class/);
=head1 NAME
=head3 Arguments: ($source, \%$attrs)
The resultset constructor. Takes a source object (usually a
-L<DBIx::Class::ResultSourceProxy::Table>) and an attribute hash (see L</ATRRIBUTES>
+L<DBIx::Class::ResultSourceProxy::Table>) and an attribute hash (see L</ATTRIBUTES>
below). Does not perform any queries -- these are executed as needed by the
other methods.
if $attrs->{order_by} && !ref($attrs->{order_by});
$attrs->{order_by} ||= [];
+ my $collapse = {};
+
if (my $prefetch = delete $attrs->{prefetch}) {
+ my @pre_order;
foreach my $p (ref $prefetch eq 'ARRAY'
? (@{$prefetch}) : ($prefetch)) {
if( ref $p eq 'HASH' ) {
unless $seen{$p};
}
my @prefetch = $source->resolve_prefetch(
- $p, $attrs->{alias}, {}, $attrs->{order_by});
+ $p, $attrs->{alias}, {}, \@pre_order, $collapse);
#die Dumper \@cols;
push(@{$attrs->{select}}, map { $_->[0] } @prefetch);
push(@{$attrs->{as}}, map { $_->[1] } @prefetch);
}
+ push(@{$attrs->{order_by}}, @pre_order);
}
if ($attrs->{page}) {
$attrs->{offset} ||= 0;
$attrs->{offset} += ($attrs->{rows} * ($attrs->{page} - 1));
}
+
+#if (keys %{$collapse}) {
+# use Data::Dumper; warn Dumper($collapse);
+#}
+
my $new = {
result_source => $source,
+ result_class => $attrs->{result_class} || $source->result_class,
cond => $attrs->{where},
from => $attrs->{from},
+ collapse => $collapse,
count => undef,
page => delete $attrs->{page},
pager => undef,
return $obj;
}
if ($self->{attrs}{cache}) {
- $self->{all_cache_position} = 0;
+ $self->{all_cache_position} = 1;
return ($self->all)[0];
}
- my @row = delete $self->{stashed_row} || $self->cursor->next;
+ my @row = (exists $self->{stashed_row}
+ ? @{delete $self->{stashed_row}}
+ : $self->cursor->next);
# warn Dumper(\@row); use Data::Dumper;
return unless (@row);
return $self->_construct_object(@row);
sub _construct_object {
my ($self, @row) = @_;
- my @row_orig = @row; # copy @row for key comparison later, because @row will change
my @as = @{ $self->{attrs}{as} };
-#use Data::Dumper; warn Dumper \@as;
+
+ my $info = $self->_collapse_result(\@as, \@row);
+
+ #use Data::Dumper; warn Dumper(\@as, $info);
+ 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;
+}
+
+sub _collapse_result {
+ my ($self, $as, $row, $prefix) = @_;
+
+ my %const;
+
+ my @copy = @$row;
+ foreach my $this_as (@$as) {
+ my $val = shift @copy;
+ if (defined $prefix) {
+ if ($this_as =~ m/^\Q${prefix}.\E(.+)$/) {
+ my $remain = $1;
+ $remain =~ /^(?:(.*)\.)?([^\.]+)$/;
+ $const{$1||''}{$2} = $val;
+ }
+ } else {
+ $this_as =~ /^(?:(.*)\.)?([^\.]+)$/;
+ $const{$1||''}{$2} = $val;
+ }
+ }
+
#warn "@cols -> @row";
my $info = [ {}, {} ];
- foreach my $as (@as) {
- my $rs = $self;
+ foreach my $key (keys %const) {
+ if (length $key) {
+ my $target = $info;
+ my @parts = split(/\./, $key);
+ foreach my $p (@parts) {
+ $target = $target->[1]->{$p} ||= [];
+ }
+ $target->[0] = $const{$key};
+ } else {
+ $info->[0] = $const{$key};
+ }
+ }
+
+ my @collapse = (defined($prefix)
+ ? (map { (m/^\Q${prefix}.\E(.+)$/ ? ($1) : ()); }
+ keys %{$self->{collapse}})
+ : keys %{$self->{collapse}});
+ if (@collapse) {
+ my ($c) = sort { length $a <=> length $b } @collapse;
+ #warn "Collapsing ${c}";
my $target = $info;
- my @parts = split(/\./, $as);
- my $col = pop(@parts);
- foreach my $p (@parts) {
+ #warn Data::Dumper::Dumper($target);
+ foreach my $p (split(/\./, $c)) {
$target = $target->[1]->{$p} ||= [];
-
- $rs = $rs->related_resultset($p) if $rs->{attrs}->{cache};
}
-
- $target->[0]->{$col} = shift @row
- if ref($target->[0]) ne 'ARRAY'; # arrayref is pre-inflated objects, do not overwrite
+ my $c_prefix = (defined($prefix) ? "${prefix}.${c}" : $c);
+ my @co_key = @{$self->{collapse}{$c_prefix}};
+ my %co_check = map { ($_, $target->[0]->{$_}); } @co_key;
+ my $tree = $self->_collapse_result($as, $row, $c_prefix);
+ #warn Data::Dumper::Dumper($tree, $row);
+ my (@final, @raw);
+ while ( !(grep {
+ !defined($tree->[0]->{$_})
+ || $co_check{$_} ne $tree->[0]->{$_}
+ } @co_key) ) {
+ push(@final, $tree);
+ last unless (@raw = $self->cursor->next);
+ $row = $self->{stashed_row} = \@raw;
+ $tree = $self->_collapse_result($as, $row, $c_prefix);
+ #warn Data::Dumper::Dumper($tree, $row);
+ }
+ @{$target} = @final;
+ #warn Data::Dumper::Dumper($target);
+ #warn Data::Dumper::Dumper($info);
}
- #use Data::Dumper; warn Dumper(\@as, $info);
- my $new = $self->result_source->result_class->inflate_result(
- $self->result_source, @$info);
- $new = $self->{attrs}{record_filter}->($new)
- if exists $self->{attrs}{record_filter};
-
- return $new;
+
+ #warn Dumper($info);
+
+ return $info;
}
=head2 result_source
my ($self) = @_;
return @{ $self->get_cache }
if @{ $self->get_cache };
+
+ my @obj;
+
+ if (keys %{$self->{collapse}}) {
+ # Using $self->cursor->all is really just an optimisation.
+ # If we're collapsing has_many prefetches it probably makes
+ # very little difference, and this is cleaner than hacking
+ # _construct_object to survive the approach
+ my @row;
+ $self->cursor->reset;
+ while (@row = $self->cursor->next) {
+ push(@obj, $self->_construct_object(@row));
+ }
+ } else {
+ @obj = map { $self->_construct_object(@$_); }
+ $self->cursor->all;
+ }
+
if( $self->{attrs}->{cache} ) {
- my @obj = map { $self->_construct_object(@$_); }
- $self->cursor->all;
$self->set_cache( \@obj );
- return @obj;
}
- return map { $self->_construct_object(@$_); }
- $self->cursor->all;
+
+ return @obj;
}
=head2 reset
foreach my $key (keys %{$self->{cond}||{}}) {
$new{$1} = $self->{cond}{$key} if ($key =~ m/^(?:$alias\.)?([^\.]+)$/);
}
- my $obj = $self->result_source->result_class->new(\%new);
+ my $obj = $self->result_class->new(\%new);
$obj->result_source($self->result_source) if $obj->can('result_source');
$obj;
}
my ( $self, $data ) = @_;
$self->throw_exception("set_cache requires an arrayref")
if ref $data ne 'ARRAY';
- my $result_class = $self->result_source->result_class;
+ my $result_class = $self->result_class;
foreach( @$data ) {
$self->throw_exception("cannot cache object of type '$_', expected '$result_class'")
if ref $_ ne $result_class;