}
$attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct};
+ $attrs->{order_by} = [ $attrs->{order_by} ]
+ if $attrs->{order_by} && !ref($attrs->{order_by});
+ $attrs->{order_by} ||= [];
+
if (my $prefetch = delete $attrs->{prefetch}) {
foreach my $p (ref $prefetch eq 'ARRAY'
? (@{$prefetch}) : ($prefetch)) {
push(@{$attrs->{from}}, $source->resolve_join($p, $attrs->{alias}))
unless $seen{$p};
}
- my @prefetch = $source->resolve_prefetch($p, $attrs->{alias});
+ my @prefetch = $source->resolve_prefetch(
+ $p, $attrs->{alias}, {}, $attrs->{order_by});
#die Dumper \@cols;
push(@{$attrs->{select}}, map { $_->[0] } @prefetch);
push(@{$attrs->{as}}, map { $_->[1] } @prefetch);
sub search {
my $self = shift;
- #use Data::Dumper;warn Dumper(@_);
-
- my $attrs = { %{$self->{attrs}} };
- if (@_ > 1 && ref $_[$#_] eq 'HASH') {
- $attrs = { %$attrs, %{ pop(@_) } };
- }
+ my $rs;
+ if( @_ ) {
+
+ my $attrs = { %{$self->{attrs}} };
+ my $having = delete $attrs->{having};
+ if (@_ > 1 && ref $_[$#_] eq 'HASH') {
+ $attrs = { %$attrs, %{ pop(@_) } };
+ }
- my $where = (@_ ? ((@_ == 1 || ref $_[0] eq "HASH") ? shift : {@_}) : undef());
- if (defined $where) {
- $where = (defined $attrs->{where}
+ my $where = (@_
+ ? ((@_ == 1 || ref $_[0] eq "HASH")
+ ? shift
+ : ((@_ % 2)
+ ? $self->throw_exception(
+ "Odd number of arguments to search")
+ : {@_}))
+ : undef());
+ if (defined $where) {
+ $where = (defined $attrs->{where}
? { '-and' =>
[ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
$where, $attrs->{where} ] }
: $where);
- $attrs->{where} = $where;
- }
+ $attrs->{where} = $where;
+ }
- my $rs = (ref $self)->new($self->result_source, $attrs);
+ if (defined $having) {
+ $having = (defined $attrs->{having}
+ ? { '-and' =>
+ [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
+ $having, $attrs->{having} ] }
+ : $having);
+ $attrs->{having} = $having;
+ }
+ $rs = (ref $self)->new($self->result_source, $attrs);
+ }
+ else {
+ $rs = $self;
+ $rs->reset();
+ }
return (wantarray ? $rs->all : $rs);
}
$query->{$self->{attrs}{alias}.'.'.$_} = delete $query->{$_};
}
#warn Dumper($query);
- return $self->search($query,$attrs)->next;
+ return (keys %$attrs
+ ? $self->search($query,$attrs)->single
+ : $self->single($query));
}
=head2 search_related
=cut
sub search_related {
- my ($self, $rel, @rest) = @_;
- my $rel_obj = $self->result_source->relationship_info($rel);
- $self->throw_exception(
- "No such relationship ${rel} in search_related")
- unless $rel_obj;
- my $rs = $self->search(undef, { join => $rel });
- my $alias = ($rs->{attrs}{seen_join}{$rel} > 1
- ? join('_', $rel, $rs->{attrs}{seen_join}{$rel})
- : $rel);
- return $self->result_source->schema->resultset($rel_obj->{class}
- )->search( undef,
- { %{$rs->{attrs}},
- alias => $alias,
- select => undef(),
- as => undef() }
- )->search(@rest);
+ return shift->related_resultset(shift)->search(@_);
}
=head2 cursor
$attrs->{where},$attrs);
}
+=head2 single
+
+Inflates the first result without creating a cursor
+
+=cut
+
+sub single {
+ my ($self, $extra) = @_;
+ my ($attrs) = $self->{attrs};
+ $attrs = { %$attrs };
+ if ($extra) {
+ if (defined $attrs->{where}) {
+ $attrs->{where} = {
+ '-and'
+ => [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
+ delete $attrs->{where}, $extra ]
+ };
+ } else {
+ $attrs->{where} = $extra;
+ }
+ }
+ my @data = $self->result_source->storage->select_single(
+ $self->{from}, $attrs->{select},
+ $attrs->{where},$attrs);
+ return (@data ? $self->_construct_object(@data) : ());
+}
+
+
=head2 search_like
Perform a search, but use C<LIKE> instead of equality as the condition. Note
sub next {
my ($self) = @_;
- my @row = $self->cursor->next;
+ my $cache;
+ if( @{$cache = $self->{all_cache} || []}) {
+ $self->{all_cache_position} ||= 0;
+ my $obj = $cache->[$self->{all_cache_position}];
+ $self->{all_cache_position}++;
+ return $obj;
+ }
+ if ($self->{attrs}{cache}) {
+ $self->{all_cache_position} = 0;
+ return ($self->all)[0];
+ }
+ my @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;
#warn "@cols -> @row";
my $info = [ {}, {} ];
foreach my $as (@as) {
+ my $rs = $self;
my $target = $info;
my @parts = split(/\./, $as);
my $col = pop(@parts);
foreach my $p (@parts) {
$target = $target->[1]->{$p} ||= [];
+
+ $rs = $rs->related_resultset($p) if $rs->{attrs}->{cache};
}
- $target->[0]->{$col} = shift @row;
+
+ $target->[0]->{$col} = shift @row
+ if ref($target->[0]) ne 'ARRAY'; # arrayref is pre-inflated objects, do not overwrite
}
#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;
}
my $self = shift;
return $self->search(@_)->count if @_ && defined $_[0];
unless (defined $self->{count}) {
+ return scalar @{ $self->get_cache }
+ if @{ $self->get_cache };
my $group_by;
my $select = { 'count' => '*' };
- if( $group_by = delete $self->{attrs}{group_by} ) {
+ my $attrs = { %{ $self->{attrs} } };
+ if( $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( scalar(@pk) == 1 ) {
my $pk = shift(@pk);
- my $alias = $self->{attrs}{alias};
+ my $alias = $attrs->{alias};
my $re = qr/^($alias\.)?$pk$/;
foreach my $column ( @distinct) {
if( $column =~ $re ) {
#use Data::Dumper; die Dumper $select;
}
- my $attrs = { %{ $self->{attrs} },
- select => $select,
- as => [ 'count' ] };
+ $attrs->{select} = $select;
+ $attrs->{as} = [ '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/;
($self->{count}) = (ref $self)->new($self->result_source, $attrs)->cursor->next;
- $self->{attrs}{group_by} = $group_by;
}
return 0 unless $self->{count};
my $count = $self->{count};
sub all {
my ($self) = @_;
+ return @{ $self->get_cache }
+ if @{ $self->get_cache };
+ 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;
}
sub reset {
my ($self) = @_;
+ $self->{all_cache_position} = 0;
$self->cursor->reset;
return $self;
}
return $row;
}
+=head2 get_cache
+
+Gets the contents of the cache for the resultset.
+
+=cut
+
+sub get_cache {
+ my $self = shift;
+ return $self->{all_cache} || [];
+}
+
+=head2 set_cache
+
+Sets the contents of the cache for the resultset. Expects an arrayref of objects of the same class as those produced by the resultset.
+
+=cut
+
+sub set_cache {
+ my ( $self, $data ) = @_;
+ $self->throw_exception("set_cache requires an arrayref")
+ if ref $data ne 'ARRAY';
+ my $result_class = $self->result_source->result_class;
+ foreach( @$data ) {
+ $self->throw_exception("cannot cache object of type '$_', expected '$result_class'")
+ if ref $_ ne $result_class;
+ }
+ $self->{all_cache} = $data;
+}
+
+=head2 clear_cache
+
+Clears the cache for the resultset.
+
+=cut
+
+sub clear_cache {
+ my $self = shift;
+ $self->set_cache([]);
+}
+
+=head2 related_resultset
+
+Returns a related resultset for the supplied relationship name.
+
+ $rs = $rs->related_resultset('foo');
+
+=cut
+
+sub related_resultset {
+ my ( $self, $rel, @rest ) = @_;
+ $self->{related_resultsets} ||= {};
+ my $resultsets = $self->{related_resultsets};
+ if( !exists $resultsets->{$rel} ) {
+ #warn "fetching related resultset for rel '$rel'";
+ my $rel_obj = $self->result_source->relationship_info($rel);
+ $self->throw_exception(
+ "search_related: result source '" . $self->result_source->name .
+ "' has no such relationship ${rel}")
+ unless $rel_obj; #die Dumper $self->{attrs};
+ my $rs = $self->search(undef, { join => $rel });
+ #if( $self->{attrs}->{cache} ) {
+ # $rs = $self->search(undef);
+ #}
+ #else {
+ #}
+ #use Data::Dumper; die Dumper $rs->{attrs};#$rs = $self->search( undef );
+ #use Data::Dumper; warn Dumper $self->{attrs}, Dumper $rs->{attrs};
+ my $alias = (defined $rs->{attrs}{seen_join}{$rel}
+ && $rs->{attrs}{seen_join}{$rel} > 1
+ ? join('_', $rel, $rs->{attrs}{seen_join}{$rel})
+ : $rel);
+ $resultsets->{$rel} =
+ $self->result_source->schema->resultset($rel_obj->{class}
+ )->search( undef,
+ { %{$rs->{attrs}},
+ alias => $alias,
+ select => undef(),
+ as => undef() }
+ )->search(@rest);
+ }
+ return $resultsets->{$rel};
+}
+
=head2 throw_exception
See Schema's throw_exception