Add unique constraint declaration and new ResultSet method, update_or_create
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSet.pm
CommitLineData
89c0a5a2 1package DBIx::Class::ResultSet;
2
3use strict;
4use warnings;
aa562407 5use Carp qw/croak/;
89c0a5a2 6use overload
7 '0+' => 'count',
a910dc57 8 'bool' => sub { 1; },
89c0a5a2 9 fallback => 1;
3c5b25c5 10use Data::Page;
ea20d0fd 11use Storable;
89c0a5a2 12
ee38fa40 13=head1 NAME
14
bfab575a 15DBIx::Class::ResultSet - Responsible for fetching and creating resultset.
ee38fa40 16
bfab575a 17=head1 SYNOPSIS
ee38fa40 18
87f0da6a 19 my $rs = MyApp::DB::Class->search(registered => 1);
20 my @rows = MyApp::DB::Class->search(foo => 'bar');
ee38fa40 21
22=head1 DESCRIPTION
23
bfab575a 24The resultset is also known as an iterator. It is responsible for handling
25queries that may return an arbitrary number of rows, e.g. via C<search>
26or a C<has_many> relationship.
ee38fa40 27
28=head1 METHODS
29
976f3686 30=head2 new($source, \%$attrs)
ee38fa40 31
976f3686 32The resultset constructor. Takes a source object (usually a DBIx::Class::Table)
33and an attribute hash (see below for more information on attributes). Does
34not perform any queries -- these are executed as needed by the other methods.
ee38fa40 35
36=cut
37
89c0a5a2 38sub new {
fea3d045 39 my $class = shift;
f9db5527 40 return $class->new_result(@_) if ref $class;
fea3d045 41 my ($source, $attrs) = @_;
b98e75f6 42 #use Data::Dumper; warn Dumper($attrs);
ea20d0fd 43 $attrs = Storable::dclone($attrs || {}); # { %{ $attrs || {} } };
c7ce65e6 44 my %seen;
6aeb9185 45 my $alias = ($attrs->{alias} ||= 'me');
a9433341 46 if ($attrs->{cols} || !$attrs->{select}) {
47 delete $attrs->{as} if $attrs->{cols};
976f3686 48 my @cols = ($attrs->{cols}
49 ? @{delete $attrs->{cols}}
a9433341 50 : $source->columns);
6aeb9185 51 $attrs->{select} = [ map { m/\./ ? $_ : "${alias}.$_" } @cols ];
976f3686 52 }
6aeb9185 53 $attrs->{as} ||= [ map { m/^$alias\.(.*)$/ ? $1 : $_ } @{$attrs->{select}} ];
976f3686 54 #use Data::Dumper; warn Dumper(@{$attrs}{qw/select as/});
fea3d045 55 $attrs->{from} ||= [ { $alias => $source->from } ];
b52e9bf8 56 if (my $join = delete $attrs->{join}) {
57 foreach my $j (ref $join eq 'ARRAY'
58 ? (@{$join}) : ($join)) {
c7ce65e6 59 if (ref $j eq 'HASH') {
60 $seen{$_} = 1 foreach keys %$j;
61 } else {
62 $seen{$j} = 1;
63 }
64 }
8452e496 65 push(@{$attrs->{from}}, $source->resolve_join($join, $attrs->{alias}));
c7ce65e6 66 }
54540863 67 $attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct};
b52e9bf8 68 foreach my $pre (@{delete $attrs->{prefetch} || []}) {
8452e496 69 push(@{$attrs->{from}}, $source->resolve_join($pre, $attrs->{alias}))
c7ce65e6 70 unless $seen{$pre};
976f3686 71 my @pre =
c7ce65e6 72 map { "$pre.$_" }
f9db5527 73 $source->related_source($pre)->columns;
976f3686 74 push(@{$attrs->{select}}, @pre);
75 push(@{$attrs->{as}}, @pre);
fef5d100 76 }
6aeb9185 77 if ($attrs->{page}) {
78 $attrs->{rows} ||= 10;
79 $attrs->{offset} ||= 0;
80 $attrs->{offset} += ($attrs->{rows} * ($attrs->{page} - 1));
81 }
89c0a5a2 82 my $new = {
cda04c3a 83 source => $source,
89c0a5a2 84 cond => $attrs->{where},
0a3c5b43 85 from => $attrs->{from},
3c5b25c5 86 count => undef,
93b004d3 87 page => delete $attrs->{page},
3c5b25c5 88 pager => undef,
89c0a5a2 89 attrs => $attrs };
2f5911b2 90 bless ($new, $class);
9229f20a 91 return $new;
89c0a5a2 92}
93
bfab575a 94=head2 search
0a3c5b43 95
87f0da6a 96 my @obj = $rs->search({ foo => 3 }); # "... WHERE foo = 3"
97 my $new_rs = $rs->search({ foo => 3 });
98
6009260a 99If you need to pass in additional attributes but no additional condition,
100call it as ->search(undef, \%attrs);
87f0da6a 101
6009260a 102 my @all = $class->search({}, { cols => [qw/foo bar/] }); # "SELECT foo, bar FROM $class_table"
0a3c5b43 103
104=cut
105
106sub search {
107 my $self = shift;
108
6009260a 109 #use Data::Dumper;warn Dumper(@_);
110
0a3c5b43 111 my $attrs = { %{$self->{attrs}} };
112 if (@_ > 1 && ref $_[$#_] eq 'HASH') {
6aeb9185 113 $attrs = { %$attrs, %{ pop(@_) } };
0a3c5b43 114 }
115
6aeb9185 116 my $where = (@_ ? ((@_ == 1 || ref $_[0] eq "HASH") ? shift : {@_}) : undef());
0a3c5b43 117 if (defined $where) {
118 $where = (defined $attrs->{where}
ad3d2d7c 119 ? { '-and' =>
120 [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
121 $where, $attrs->{where} ] }
0a3c5b43 122 : $where);
123 $attrs->{where} = $where;
124 }
125
fea3d045 126 my $rs = (ref $self)->new($self->{source}, $attrs);
0a3c5b43 127
128 return (wantarray ? $rs->all : $rs);
129}
130
87f0da6a 131=head2 search_literal
132
6009260a 133 my @obj = $rs->search_literal($literal_where_cond, @bind);
134 my $new_rs = $rs->search_literal($literal_where_cond, @bind);
135
136Pass a literal chunk of SQL to be added to the conditional part of the
87f0da6a 137resultset.
6009260a 138
bfab575a 139=cut
140
6009260a 141sub search_literal {
142 my ($self, $cond, @vals) = @_;
143 my $attrs = (ref $vals[$#vals] eq 'HASH' ? { %{ pop(@vals) } } : {});
144 $attrs->{bind} = [ @{$self->{attrs}{bind}||[]}, @vals ];
145 return $self->search(\$cond, $attrs);
146}
0a3c5b43 147
87f0da6a 148=head2 find(@colvalues), find(\%cols, \%attrs?)
149
150Finds a row based on its primary key or unique constraint. For example:
151
152 # In your table class
153 package MyApp::Schema::CD;
154
155 __PACKAGE__->table('cd');
156 __PACKAGE__->add_columns(qw/cdid artist title year/);
157 __PACKAGE__->set_primary_key('cdid');
158 __PACKAGE__->add_unique_constraint(artist_title => [ qw/artist title/ ]);
716b3d29 159
87f0da6a 160 1;
716b3d29 161
87f0da6a 162 # In your application
163 my $cd = $schema->resultset('CD')->find(5);
164
165Also takes an optional C<key> attribute, to search by a specific key or unique
166constraint. For example:
167
168 my $cd = $schema->resultset('CD')->find_or_create(
169 {
170 artist => 'Massive Attack',
171 title => 'Mezzanine',
172 },
173 { key => 'artist_title' }
174 );
175
176=cut
716b3d29 177
178sub find {
179 my ($self, @vals) = @_;
180 my $attrs = (@vals > 1 && ref $vals[$#vals] eq 'HASH' ? pop(@vals) : {});
87f0da6a 181
182 my @cols = $self->{source}->primary_columns;
183 if (exists $attrs->{key}) {
184 my %uniq = $self->{source}->unique_constraints;
185 $self->( "Unknown key " . $attrs->{key} . " on " . $self->name )
186 unless exists $uniq{$attrs->{key}};
187 @cols = @{ $uniq{$attrs->{key}} };
188 }
189 #use Data::Dumper; warn Dumper($attrs, @vals, @cols);
190 $self->{source}->result_class->throw( "Can't find unless a primary key or unique constraint is defined" )
191 unless @cols;
192
716b3d29 193 my $query;
194 if (ref $vals[0] eq 'HASH') {
195 $query = $vals[0];
87f0da6a 196 } elsif (@cols == @vals) {
716b3d29 197 $query = {};
87f0da6a 198 @{$query}{@cols} = @vals;
716b3d29 199 } else {
200 $query = {@vals};
201 }
202 #warn Dumper($query);
203 # Useless -> disabled
204 #$self->{source}->result_class->throw( "Can't find unless all primary keys are specified" )
205 # unless (keys %$query >= @pk); # If we check 'em we run afoul of uc/lc
206 # column names etc. Not sure what to do yet
207 return $self->search($query)->next;
208}
209
b52e9bf8 210=head2 search_related
211
212 $rs->search_related('relname', $cond?, $attrs?);
213
214=cut
215
6aeb9185 216sub search_related {
217 my ($self, $rel, @rest) = @_;
f9db5527 218 my $rel_obj = $self->{source}->relationship_info($rel);
6aeb9185 219 $self->{source}->result_class->throw(
220 "No such relationship ${rel} in search_related")
221 unless $rel_obj;
6aeb9185 222 my $rs = $self->search(undef, { join => $rel });
ea20d0fd 223 return $self->{source}->schema->resultset($rel_obj->{class}
224 )->search( undef,
225 { %{$rs->{attrs}},
226 alias => $rel,
227 select => undef(),
228 as => undef() }
6aeb9185 229 )->search(@rest);
230}
b52e9bf8 231
bfab575a 232=head2 cursor
ee38fa40 233
bfab575a 234Returns a storage-driven cursor to the given resultset.
ee38fa40 235
236=cut
237
73f58123 238sub cursor {
239 my ($self) = @_;
2f5911b2 240 my ($source, $attrs) = @{$self}{qw/source attrs/};
6aeb9185 241 $attrs = { %$attrs };
73f58123 242 return $self->{cursor}
976f3686 243 ||= $source->storage->select($self->{from}, $attrs->{select},
73f58123 244 $attrs->{where},$attrs);
245}
246
87f0da6a 247=head2 search_like
248
249Identical to search except defaults to 'LIKE' instead of '=' in condition
250
251=cut
58a4bd18 252
253sub search_like {
254 my $class = shift;
255 my $attrs = { };
256 if (@_ > 1 && ref $_[$#_] eq 'HASH') {
257 $attrs = pop(@_);
258 }
259 my $query = ref $_[0] eq "HASH" ? { %{shift()} }: {@_};
260 $query->{$_} = { 'like' => $query->{$_} } for keys %$query;
261 return $class->search($query, { %$attrs });
262}
263
bfab575a 264=head2 slice($first, $last)
ee38fa40 265
bfab575a 266Returns a subset of elements from the resultset.
ee38fa40 267
268=cut
269
89c0a5a2 270sub slice {
271 my ($self, $min, $max) = @_;
272 my $attrs = { %{ $self->{attrs} || {} } };
6aeb9185 273 $attrs->{offset} ||= 0;
274 $attrs->{offset} += $min;
89c0a5a2 275 $attrs->{rows} = ($max ? ($max - $min + 1) : 1);
fea3d045 276 my $slice = (ref $self)->new($self->{source}, $attrs);
89c0a5a2 277 return (wantarray ? $slice->all : $slice);
278}
279
87f0da6a 280=head2 next
ee38fa40 281
bfab575a 282Returns the next element in the resultset (undef is there is none).
ee38fa40 283
284=cut
285
89c0a5a2 286sub next {
287 my ($self) = @_;
73f58123 288 my @row = $self->cursor->next;
a953d8d9 289# warn Dumper(\@row); use Data::Dumper;
89c0a5a2 290 return unless (@row);
c7ce65e6 291 return $self->_construct_object(@row);
292}
293
294sub _construct_object {
295 my ($self, @row) = @_;
976f3686 296 my @cols = @{ $self->{attrs}{as} };
297 #warn "@cols -> @row";
b52e9bf8 298 my (%me, %pre);
299 foreach my $col (@cols) {
300 if ($col =~ /([^\.]+)\.([^\.]+)/) {
6aeb9185 301 $pre{$1}[0]{$2} = shift @row;
b52e9bf8 302 } else {
303 $me{$col} = shift @row;
c7ce65e6 304 }
c7ce65e6 305 }
c01ab172 306 my $new = $self->{source}->result_class->inflate_result(
307 $self->{source}, \%me, \%pre);
33ce49d6 308 $new = $self->{attrs}{record_filter}->($new)
309 if exists $self->{attrs}{record_filter};
310 return $new;
89c0a5a2 311}
312
bfab575a 313=head2 count
ee38fa40 314
bfab575a 315Performs an SQL C<COUNT> with the same query as the resultset was built
6009260a 316with to find the number of elements. If passed arguments, does a search
317on the resultset and counts the results of that.
ee38fa40 318
319=cut
320
89c0a5a2 321sub count {
6009260a 322 my $self = shift;
323 return $self->search(@_)->count if @_ && defined $_[0];
aa562407 324 croak "Unable to ->count with a GROUP BY" if defined $self->{attrs}{group_by};
6aeb9185 325 unless (defined $self->{count}) {
976f3686 326 my $attrs = { %{ $self->{attrs} },
54540863 327 select => { 'count' => '*' },
328 as => [ 'count' ] };
ea20d0fd 329 # offset, order by and page are not needed to count. record_filter is cdbi
330 delete $attrs->{$_} for qw/rows offset order_by page pager record_filter/;
3c5b25c5 331
fea3d045 332 ($self->{count}) = (ref $self)->new($self->{source}, $attrs)->cursor->next;
3c5b25c5 333 }
334 return 0 unless $self->{count};
6aeb9185 335 my $count = $self->{count};
336 $count -= $self->{attrs}{offset} if $self->{attrs}{offset};
337 $count = $self->{attrs}{rows} if
338 ($self->{attrs}{rows} && $self->{attrs}{rows} < $count);
339 return $count;
89c0a5a2 340}
341
bfab575a 342=head2 count_literal
6009260a 343
bfab575a 344Calls search_literal with the passed arguments, then count.
6009260a 345
346=cut
347
348sub count_literal { shift->search_literal(@_)->count; }
349
bfab575a 350=head2 all
ee38fa40 351
bfab575a 352Returns all elements in the resultset. Called implictly if the resultset
353is returned in list context.
ee38fa40 354
355=cut
356
89c0a5a2 357sub all {
358 my ($self) = @_;
c7ce65e6 359 return map { $self->_construct_object(@$_); }
73f58123 360 $self->cursor->all;
89c0a5a2 361}
362
bfab575a 363=head2 reset
ee38fa40 364
bfab575a 365Resets the resultset's cursor, so you can iterate through the elements again.
ee38fa40 366
367=cut
368
89c0a5a2 369sub reset {
370 my ($self) = @_;
73f58123 371 $self->cursor->reset;
89c0a5a2 372 return $self;
373}
374
bfab575a 375=head2 first
ee38fa40 376
bfab575a 377Resets the resultset and returns the first element.
ee38fa40 378
379=cut
380
89c0a5a2 381sub first {
382 return $_[0]->reset->next;
383}
384
c01ab172 385=head2 update(\%values)
386
387Sets the specified columns in the resultset to the supplied values
388
389=cut
390
391sub update {
392 my ($self, $values) = @_;
aa562407 393 croak "Values for update must be a hash" unless ref $values eq 'HASH';
c01ab172 394 return $self->{source}->storage->update(
395 $self->{source}->from, $values, $self->{cond});
396}
397
398=head2 update_all(\%values)
399
400Fetches all objects and updates them one at a time. ->update_all will run
401cascade triggers, ->update will not.
402
403=cut
404
405sub update_all {
406 my ($self, $values) = @_;
aa562407 407 croak "Values for update must be a hash" unless ref $values eq 'HASH';
c01ab172 408 foreach my $obj ($self->all) {
409 $obj->set_columns($values)->update;
410 }
411 return 1;
412}
413
bfab575a 414=head2 delete
ee38fa40 415
c01ab172 416Deletes the contents of the resultset from its result source.
ee38fa40 417
418=cut
419
28927b50 420sub delete {
89c0a5a2 421 my ($self) = @_;
c01ab172 422 $self->{source}->storage->delete($self->{source}->from, $self->{cond});
89c0a5a2 423 return 1;
424}
425
c01ab172 426=head2 delete_all
427
428Fetches all objects and deletes them one at a time. ->delete_all will run
429cascade triggers, ->delete will not.
430
431=cut
432
433sub delete_all {
434 my ($self) = @_;
435 $_->delete for $self->all;
436 return 1;
437}
28927b50 438
bfab575a 439=head2 pager
ee38fa40 440
441Returns a L<Data::Page> object for the current resultset. Only makes
442sense for queries with page turned on.
443
444=cut
445
3c5b25c5 446sub pager {
447 my ($self) = @_;
448 my $attrs = $self->{attrs};
aa562407 449 croak "Can't create pager for non-paged rs" unless $self->{page};
6aeb9185 450 $attrs->{rows} ||= 10;
451 $self->count;
452 return $self->{pager} ||= Data::Page->new(
93b004d3 453 $self->{count}, $attrs->{rows}, $self->{page});
3c5b25c5 454}
455
bfab575a 456=head2 page($page_num)
ee38fa40 457
bfab575a 458Returns a new resultset for the specified page.
ee38fa40 459
460=cut
461
3c5b25c5 462sub page {
463 my ($self, $page) = @_;
6aeb9185 464 my $attrs = { %{$self->{attrs}} };
3c5b25c5 465 $attrs->{page} = $page;
fea3d045 466 return (ref $self)->new($self->{source}, $attrs);
467}
468
469=head2 new_result(\%vals)
470
87f0da6a 471Creates a result in the resultset's result class.
fea3d045 472
473=cut
474
475sub new_result {
476 my ($self, $values) = @_;
477 $self->{source}->result_class->throw( "new_result needs a hash" )
478 unless (ref $values eq 'HASH');
479 $self->{source}->result_class->throw( "Can't abstract implicit construct, condition not a hash" )
480 if ($self->{cond} && !(ref $self->{cond} eq 'HASH'));
481 my %new = %$values;
482 my $alias = $self->{attrs}{alias};
483 foreach my $key (keys %{$self->{cond}||{}}) {
484 $new{$1} = $self->{cond}{$key} if ($key =~ m/^(?:$alias\.)?([^\.]+)$/);
485 }
097d3227 486 my $obj = $self->{source}->result_class->new(\%new);
487 $obj->result_source($self->{source}) if $obj->can('result_source');
488 $obj;
fea3d045 489}
490
491=head2 create(\%vals)
492
87f0da6a 493Inserts a record into the resultset and returns the object.
fea3d045 494
495Effectively a shortcut for ->new_result(\%vals)->insert
496
497=cut
498
499sub create {
500 my ($self, $attrs) = @_;
501 $self->{source}->result_class->throw( "create needs a hashref" ) unless ref $attrs eq 'HASH';
502 return $self->new_result($attrs)->insert;
3c5b25c5 503}
504
87f0da6a 505=head2 find_or_create(\%vals, \%attrs?)
506
507 $class->find_or_create({ key => $val, ... });
c2b15ecc 508
c2b15ecc 509Searches for a record matching the search condition; if it doesn't find one,
510creates one and returns that instead.
87f0da6a 511
512 # In your table class
513 package MyApp::Schema::CD;
514
515 __PACKAGE__->table('cd');
516 __PACKAGE__->add_columns(qw/cdid artist title year/);
517 __PACKAGE__->set_primary_key('cdid');
518 __PACKAGE__->add_unique_constraint(artist_title => [ qw/artist title/ ]);
519
520 1;
521
522 # In your application
523 my $cd = $schema->resultset('CD')->find_or_create({
524 cdid => 5,
525 artist => 'Massive Attack',
526 title => 'Mezzanine',
527 year => 2005,
528 });
529
530Also takes an optional C<key> attribute, to search by a specific key or unique
531constraint. For example:
532
533 my $cd = $schema->resultset('CD')->find_or_create(
534 {
535 artist => 'Massive Attack',
536 title => 'Mezzanine',
537 },
538 { key => 'artist_title' }
539 );
540
541See also L</find> and L</update_or_create>.
542
c2b15ecc 543=cut
544
545sub find_or_create {
546 my $self = shift;
87f0da6a 547 my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
548 my $hash = ref $_[0] eq "HASH" ? shift : {@_};
549 my $exists = $self->find($hash, $attrs);
c2b15ecc 550 return defined($exists) ? $exists : $self->create($hash);
551}
552
87f0da6a 553=head2 update_or_create
554
555 $class->update_or_create({ key => $val, ... });
556
557First, search for an existing row matching one of the unique constraints
558(including the primary key) on the source of this resultset. If a row is
559found, update it with the other given column values. Otherwise, create a new
560row.
561
562Takes an optional C<key> attribute to search on a specific unique constraint.
563For example:
564
565 # In your application
566 my $cd = $schema->resultset('CD')->update_or_create(
567 {
568 artist => 'Massive Attack',
569 title => 'Mezzanine',
570 year => 1998,
571 },
572 { key => 'artist_title' }
573 );
574
575If no C<key> is specified, it searches on all unique constraints defined on the
576source, including the primary key.
577
578If the C<key> is specified as C<primary>, search only on the primary key.
579
580=cut
581
582sub update_or_create {
583 my $self = shift;
584
585 my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
586 my $hash = ref $_[0] eq "HASH" ? shift : {@_};
587
588 my %unique_constraints = $self->{source}->unique_constraints;
589 my @constraint_names = (exists $attrs->{key}
590 ? ($attrs->{key})
591 : keys %unique_constraints);
592
593 my @unique_hashes;
594 foreach my $name (@constraint_names) {
595 my @unique_cols = @{ $unique_constraints{$name} };
596 my %unique_hash =
597 map { $_ => $hash->{$_} }
598 grep { exists $hash->{$_} }
599 @unique_cols;
600
601 push @unique_hashes, \%unique_hash
602 if (scalar keys %unique_hash == scalar @unique_cols);
603 }
604
605 my $row;
606 if (@unique_hashes) {
607 $row = $self->search(\@unique_hashes, { rows => 1 })->first;
608 if ($row) {
609 $row->set_columns($hash);
610 $row->update;
611 }
612 }
613
614 unless ($row) {
615 $row = $self->create($hash);
616 }
617
618 return $row;
619}
620
40dbc108 621=head1 ATTRIBUTES
076652e8 622
bfab575a 623The resultset takes various attributes that modify its behavior.
624Here's an overview of them:
625
626=head2 order_by
076652e8 627
bfab575a 628Which column(s) to order the results by. This is currently passed
629through directly to SQL, so you can give e.g. C<foo DESC> for a
630descending order.
076652e8 631
976f3686 632=head2 cols (arrayref)
633
634Shortcut to request a particular set of columns to be retrieved - adds
635'me.' onto the start of any column without a '.' in it and sets 'select'
636from that, then auto-populates 'as' from 'select' as normal
637
638=head2 select (arrayref)
639
640Indicates which columns should be selected from the storage
641
642=head2 as (arrayref)
076652e8 643
976f3686 644Indicates column names for object inflation
ee38fa40 645
bfab575a 646=head2 join
ee38fa40 647
bfab575a 648Contains a list of relationships that should be joined for this query. Can also
649contain a hash reference to refer to that relation's relations. So, if one column
650in your class C<belongs_to> foo and another C<belongs_to> bar, you can do
651C<< join => [qw/ foo bar /] >> to join both (and e.g. use them for C<order_by>).
652If a foo contains many margles and you want to join those too, you can do
653C<< join => { foo => 'margle' } >>. If you want to fetch the columns from the
654related table as well, see C<prefetch> below.
ee38fa40 655
bfab575a 656=head2 prefetch
ee38fa40 657
bfab575a 658Contains a list of relationships that should be fetched along with the main
659query (when they are accessed afterwards they will have already been
660"prefetched"). This is useful for when you know you will need the related
661object(s), because it saves a query. Currently limited to prefetching
662one relationship deep, so unlike C<join>, prefetch must be an arrayref.
ee38fa40 663
bfab575a 664=head2 from
ee38fa40 665
bfab575a 666This attribute can contain a arrayref of elements. Each element can be another
ee38fa40 667arrayref, to nest joins, or it can be a hash which represents the two sides
668of the join.
669
bfab575a 670NOTE: Use this on your own risk. This allows you to shoot your foot off!
ee38fa40 671
bfab575a 672=head2 page
076652e8 673
bfab575a 674For a paged resultset, specifies which page to retrieve. Leave unset
675for an unpaged resultset.
076652e8 676
bfab575a 677=head2 rows
076652e8 678
bfab575a 679For a paged resultset, how many rows per page
076652e8 680
675ce4a6 681=head2 group_by (listref)
54540863 682
675ce4a6 683A listref of columns to group by (note that 'count' doesn't work on grouped
54540863 684resultsets)
685
675ce4a6 686 group_by => [qw/ column1 column2 ... /]
687
54540863 688=head2 distinct
689
690Set to 1 to group by all columns
691
bfab575a 692=cut
076652e8 693
89c0a5a2 6941;