8d683e04dea1d34c7d8ce0a44f850682795395d0
[dbsrgits/DBIx-Class-Historic.git] / lib / DBIx / Class / ResultSet.pm
1 package DBIx::Class::ResultSet;
2
3 use strict;
4 use warnings;
5 use overload
6         '0+'     => \&count,
7         'bool'   => sub { 1; },
8         fallback => 1;
9 use Data::Page;
10 use Storable;
11 use Scalar::Util qw/weaken/;
12
13 use DBIx::Class::ResultSetColumn;
14 use base qw/DBIx::Class/;
15 __PACKAGE__->load_components(qw/AccessorGroup/);
16 __PACKAGE__->mk_group_accessors('simple' => qw/result_source result_class/);
17
18 =head1 NAME
19
20 DBIx::Class::ResultSet - Responsible for fetching and creating resultset.
21
22 =head1 SYNOPSIS
23
24   my $rs   = $schema->resultset('User')->search(registered => 1);
25   my @rows = $schema->resultset('CD')->search(year => 2005);
26
27 =head1 DESCRIPTION
28
29 The resultset is also known as an iterator. It is responsible for handling
30 queries that may return an arbitrary number of rows, e.g. via L</search>
31 or a C<has_many> relationship.
32
33 In the examples below, the following table classes are used:
34
35   package MyApp::Schema::Artist;
36   use base qw/DBIx::Class/;
37   __PACKAGE__->load_components(qw/Core/);
38   __PACKAGE__->table('artist');
39   __PACKAGE__->add_columns(qw/artistid name/);
40   __PACKAGE__->set_primary_key('artistid');
41   __PACKAGE__->has_many(cds => 'MyApp::Schema::CD');
42   1;
43
44   package MyApp::Schema::CD;
45   use base qw/DBIx::Class/;
46   __PACKAGE__->load_components(qw/Core/);
47   __PACKAGE__->table('cd');
48   __PACKAGE__->add_columns(qw/cdid artist title year/);
49   __PACKAGE__->set_primary_key('cdid');
50   __PACKAGE__->belongs_to(artist => 'MyApp::Schema::Artist');
51   1;
52
53 =head1 METHODS
54
55 =head2 new
56
57 =over 4
58
59 =item Arguments: $source, \%$attrs
60
61 =item Return Value: $rs
62
63 =back
64
65 The resultset constructor. Takes a source object (usually a
66 L<DBIx::Class::ResultSourceProxy::Table>) and an attribute hash (see
67 L</ATTRIBUTES> below).  Does not perform any queries -- these are
68 executed as needed by the other methods.
69
70 Generally you won't need to construct a resultset manually.  You'll
71 automatically get one from e.g. a L</search> called in scalar context:
72
73   my $rs = $schema->resultset('CD')->search({ title => '100th Window' });
74
75 IMPORTANT: If called on an object, proxies to new_result instead so
76
77   my $cd = $schema->resultset('CD')->new({ title => 'Spoon' });
78
79 will return a CD object, not a ResultSet.
80
81 =cut
82
83 sub new {
84   my $class = shift;
85   return $class->new_result(@_) if ref $class;
86   
87   my ($source, $attrs) = @_;
88   weaken $source;
89   $attrs = Storable::dclone($attrs || {}); # { %{ $attrs || {} } };
90   #use Data::Dumper; warn Dumper($attrs);
91   my $alias = ($attrs->{alias} ||= 'me');
92   
93   $attrs->{columns} ||= delete $attrs->{cols} if $attrs->{cols};
94   delete $attrs->{as} if $attrs->{columns};
95   $attrs->{columns} ||= [ $source->columns ] unless $attrs->{select};
96   $attrs->{select} = [
97     map { m/\./ ? $_ : "${alias}.$_" } @{delete $attrs->{columns}}
98   ] if $attrs->{columns};
99   $attrs->{as} ||= [
100     map { m/^\Q$alias.\E(.+)$/ ? $1 : $_ } @{$attrs->{select}}
101   ];
102   if (my $include = delete $attrs->{include_columns}) {
103     push(@{$attrs->{select}}, @$include);
104     push(@{$attrs->{as}}, map { m/([^.]+)$/; $1; } @$include);
105   }
106   #use Data::Dumper; warn Dumper(@{$attrs}{qw/select as/});
107
108   $attrs->{from} ||= [ { $alias => $source->from } ];
109   $attrs->{seen_join} ||= {};
110   my %seen;
111   if (my $join = delete $attrs->{join}) {
112     foreach my $j (ref $join eq 'ARRAY' ? @$join : ($join)) {
113       if (ref $j eq 'HASH') {
114         $seen{$_} = 1 foreach keys %$j;
115       } else {
116         $seen{$j} = 1;
117       }
118     }
119     push(@{$attrs->{from}}, $source->resolve_join(
120       $join, $attrs->{alias}, $attrs->{seen_join})
121     );
122   }
123   
124   $attrs->{group_by} ||= $attrs->{select} if delete $attrs->{distinct};
125   $attrs->{order_by} = [ $attrs->{order_by} ] if
126     $attrs->{order_by} and !ref($attrs->{order_by});
127   $attrs->{order_by} ||= [];
128
129   my $collapse = $attrs->{collapse} || {};
130   if (my $prefetch = delete $attrs->{prefetch}) {
131     my @pre_order;
132     foreach my $p (ref $prefetch eq 'ARRAY' ? @$prefetch : ($prefetch)) {
133       if ( ref $p eq 'HASH' ) {
134         foreach my $key (keys %$p) {
135           push(@{$attrs->{from}}, $source->resolve_join($p, $attrs->{alias}))
136             unless $seen{$key};
137         }
138       } else {
139         push(@{$attrs->{from}}, $source->resolve_join($p, $attrs->{alias}))
140             unless $seen{$p};
141       }
142       my @prefetch = $source->resolve_prefetch(
143            $p, $attrs->{alias}, {}, \@pre_order, $collapse);
144       push(@{$attrs->{select}}, map { $_->[0] } @prefetch);
145       push(@{$attrs->{as}}, map { $_->[1] } @prefetch);
146     }
147     push(@{$attrs->{order_by}}, @pre_order);
148   }
149   $attrs->{collapse} = $collapse;
150 #  use Data::Dumper; warn Dumper($collapse) if keys %{$collapse};
151
152   if ($attrs->{page}) {
153     $attrs->{rows} ||= 10;
154     $attrs->{offset} ||= 0;
155     $attrs->{offset} += ($attrs->{rows} * ($attrs->{page} - 1));
156   }
157
158   bless {
159     result_source => $source,
160     result_class => $attrs->{result_class} || $source->result_class,
161     cond => $attrs->{where},
162     from => $attrs->{from},
163     collapse => $collapse,
164     count => undef,
165     page => delete $attrs->{page},
166     pager => undef,
167     attrs => $attrs
168   }, $class;
169 }
170
171 =head2 search
172
173 =over 4
174
175 =item Arguments: $cond, \%attrs?
176
177 =item Return Value: $resultset (scalar context), @row_objs (list context)
178
179 =back
180
181   my @cds    = $cd_rs->search({ year => 2001 }); # "... WHERE year = 2001"
182   my $new_rs = $cd_rs->search({ year => 2005 });
183
184   my $new_rs = $cd_rs->search([ { year => 2005 }, { year => 2004 } ]);
185                  # year = 2005 OR year = 2004
186
187 If you need to pass in additional attributes but no additional condition,
188 call it as C<search(undef, \%attrs)>.
189
190   # "SELECT name, artistid FROM $artist_table"
191   my @all_artists = $schema->resultset('Artist')->search(undef, {
192     columns => [qw/name artistid/],
193   });
194
195 =cut
196
197 sub search {
198   my $self = shift;
199
200   my $rs;
201   if( @_ ) {
202     
203     my $attrs = { %{$self->{attrs}} };
204     my $having = delete $attrs->{having};
205     $attrs = { %$attrs, %{ pop(@_) } } if @_ > 1 and ref $_[$#_] eq 'HASH';
206
207     my $where = (@_
208                   ? ((@_ == 1 || ref $_[0] eq "HASH")
209                       ? shift
210                       : ((@_ % 2)
211                           ? $self->throw_exception(
212                               "Odd number of arguments to search")
213                           : {@_}))
214                   : undef());
215     if (defined $where) {
216       $attrs->{where} = (defined $attrs->{where}
217                 ? { '-and' =>
218                     [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
219                         $where, $attrs->{where} ] }
220                 : $where);
221     }
222
223     if (defined $having) {
224       $attrs->{having} = (defined $attrs->{having}
225                 ? { '-and' =>
226                     [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
227                         $having, $attrs->{having} ] }
228                 : $having);
229     }
230
231     $rs = (ref $self)->new($self->result_source, $attrs);
232   }
233   else {
234     $rs = $self;
235     $rs->reset;
236   }
237   return (wantarray ? $rs->all : $rs);
238 }
239
240 =head2 search_literal
241
242 =over 4
243
244 =item Arguments: $sql_fragment, @bind_values
245
246 =item Return Value: $resultset (scalar context), @row_objs (list context)
247
248 =back
249
250   my @cds   = $cd_rs->search_literal('year = ? AND title = ?', qw/2001 Reload/);
251   my $newrs = $artist_rs->search_literal('name = ?', 'Metallica');
252
253 Pass a literal chunk of SQL to be added to the conditional part of the
254 resultset query.
255
256 =cut
257
258 sub search_literal {
259   my ($self, $cond, @vals) = @_;
260   my $attrs = (ref $vals[$#vals] eq 'HASH' ? { %{ pop(@vals) } } : {});
261   $attrs->{bind} = [ @{$self->{attrs}{bind}||[]}, @vals ];
262   return $self->search(\$cond, $attrs);
263 }
264
265 =head2 find
266
267 =over 4
268
269 =item Arguments: @values | \%cols, \%attrs?
270
271 =item Return Value: $row_object
272
273 =back
274
275 Finds a row based on its primary key or unique constraint. For example:
276
277   my $cd = $schema->resultset('CD')->find(5);
278
279 Also takes an optional C<key> attribute, to search by a specific key or unique
280 constraint. For example:
281
282   my $cd = $schema->resultset('CD')->find(
283     {
284       artist => 'Massive Attack',
285       title  => 'Mezzanine',
286     },
287     { key => 'artist_title' }
288   );
289
290 See also L</find_or_create> and L</update_or_create>.
291
292 =cut
293
294 sub find {
295   my ($self, @vals) = @_;
296   my $attrs = (@vals > 1 && ref $vals[$#vals] eq 'HASH' ? pop(@vals) : {});
297
298   my %unique_constraints = $self->result_source->unique_constraints;
299   $self->throw_exception(
300     "Can't find unless a primary key or unique constraint is defined"
301   ) unless %unique_constraints;
302
303   my @constraint_names = keys %unique_constraints;
304   if (exists $attrs->{key}) {
305     $self->throw_exception(
306       "Unknown key $attrs->{key} on '" . $self->result_source->name . "'"
307     ) unless exists $unique_constraints{$attrs->{key}};
308
309     @constraint_names = ($attrs->{key});
310   }
311
312   my @unique_hashes;
313   foreach my $name (@constraint_names) {
314     my @unique_cols = @{ $unique_constraints{$name} };
315     my %unique_hash;
316     if (ref $vals[0] eq 'HASH') {
317       %unique_hash =
318         map  { $_ => $vals[0]->{$_} }
319         grep { exists $vals[0]->{$_} }
320         @unique_cols;
321     }
322     elsif (@unique_cols == @vals) {
323       # Assume the argument order corresponds to the constraint definition
324       @unique_hash{@unique_cols} = @vals;
325     }
326     elsif (@vals % 2 == 0) {
327       # Fix for CDBI calling with a hash
328       %unique_hash = @vals;
329     }
330
331     foreach my $key (grep { ! m/\./ } keys %unique_hash) {
332       $unique_hash{"$self->{attrs}{alias}.$key"} = delete $unique_hash{$key};
333     }
334
335     #use Data::Dumper; warn Dumper \@vals, \@unique_cols, \%unique_hash;
336     push @unique_hashes, \%unique_hash if %unique_hash;
337   }
338
339   # Handle cases where the ResultSet already defines the query
340   my $query = @unique_hashes ? \@unique_hashes : undef;
341
342   if (keys %$attrs) {
343     my $rs = $self->search($query, $attrs);
344     return keys %{$rs->{collapse}} ? $rs->next : $rs->single;
345   }
346   else {
347     return keys %{$self->{collapse}}
348       ? $self->search($query)->next
349       : $self->single($query);
350   }
351 }
352
353 =head2 search_related
354
355 =over 4
356
357 =item Arguments: $cond, \%attrs?
358
359 =item Return Value: $new_resultset
360
361 =back
362
363   $new_rs = $cd_rs->search_related('artist', {
364     name => 'Emo-R-Us',
365   });
366
367 Searches the specified relationship, optionally specifying a condition and
368 attributes for matching records. See L</ATTRIBUTES> for more information.
369
370 =cut
371
372 sub search_related {
373   return shift->related_resultset(shift)->search(@_);
374 }
375
376 =head2 cursor
377
378 =over 4
379
380 =item Arguments: none
381
382 =item Return Value: $cursor
383
384 =back
385
386 Returns a storage-driven cursor to the given resultset. See
387 L<DBIx::Class::Cursor> for more information.
388
389 =cut
390
391 sub cursor {
392   my ($self) = @_;
393   my $attrs = { %{$self->{attrs}} };
394   return $self->{cursor}
395     ||= $self->result_source->storage->select($self->{from}, $attrs->{select},
396           $attrs->{where},$attrs);
397 }
398
399 =head2 single
400
401 =over 4
402
403 =item Arguments: $cond?
404
405 =item Return Value: $row_object?
406
407 =back
408
409   my $cd = $schema->resultset('CD')->single({ year => 2001 });
410
411 Inflates the first result without creating a cursor if the resultset has
412 any records in it; if not returns nothing. Used by find() as an optimisation.
413
414 =cut
415
416 sub single {
417   my ($self, $where) = @_;
418   my $attrs = { %{$self->{attrs}} };
419   if ($where) {
420     if (defined $attrs->{where}) {
421       $attrs->{where} = {
422         '-and' =>
423             [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
424                $where, delete $attrs->{where} ]
425       };
426     } else {
427       $attrs->{where} = $where;
428     }
429   }
430   my @data = $self->result_source->storage->select_single(
431           $self->{from}, $attrs->{select},
432           $attrs->{where},$attrs);
433   return (@data ? $self->_construct_object(@data) : ());
434 }
435
436 =head2 get_column
437
438 =over 4
439
440 =item Arguments: $cond?
441
442 =item Return Value: $resultsetcolumn
443
444 =back
445
446   my $max_length = $rs->get_column('length')->max;
447
448 Returns a ResultSetColumn instance for $column based on $self
449
450 =cut
451
452 sub get_column {
453   my ($self, $column) = @_;
454
455   my $new = DBIx::Class::ResultSetColumn->new($self, $column);
456   return $new;
457 }
458
459 =head2 search_like
460
461 =over 4
462
463 =item Arguments: $cond, \%attrs?
464
465 =item Return Value: $resultset (scalar context), @row_objs (list context)
466
467 =back
468
469   # WHERE title LIKE '%blue%'
470   $cd_rs = $rs->search_like({ title => '%blue%'});
471
472 Performs a search, but uses C<LIKE> instead of C<=> as the condition. Note
473 that this is simply a convenience method. You most likely want to use
474 L</search> with specific operators.
475
476 For more information, see L<DBIx::Class::Manual::Cookbook>.
477
478 =cut
479
480 sub search_like {
481   my $class = shift;
482   my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
483   my $query = ref $_[0] eq 'HASH' ? { %{shift()} }: {@_};
484   $query->{$_} = { 'like' => $query->{$_} } for keys %$query;
485   return $class->search($query, { %$attrs });
486 }
487
488 =head2 slice
489
490 =over 4
491
492 =item Arguments: $first, $last
493
494 =item Return Value: $resultset (scalar context), @row_objs (list context)
495
496 =back
497
498 Returns a resultset or object list representing a subset of elements from the
499 resultset slice is called on. Indexes are from 0, i.e., to get the first
500 three records, call:
501
502   my ($one, $two, $three) = $rs->slice(0, 2);
503
504 =cut
505
506 sub slice {
507   my ($self, $min, $max) = @_;
508   my $attrs = {}; # = { %{ $self->{attrs} || {} } };
509   $attrs->{offset} = $self->{attrs}{offset} || 0;
510   $attrs->{offset} += $min;
511   $attrs->{rows} = ($max ? ($max - $min + 1) : 1);
512   return $self->search(undef(), $attrs);
513   #my $slice = (ref $self)->new($self->result_source, $attrs);
514   #return (wantarray ? $slice->all : $slice);
515 }
516
517 =head2 next
518
519 =over 4
520
521 =item Arguments: none
522
523 =item Return Value: $result?
524
525 =back
526
527 Returns the next element in the resultset (C<undef> is there is none).
528
529 Can be used to efficiently iterate over records in the resultset:
530
531   my $rs = $schema->resultset('CD')->search;
532   while (my $cd = $rs->next) {
533     print $cd->title;
534   }
535
536 Note that you need to store the resultset object, and call C<next> on it. 
537 Calling C<< resultset('Table')->next >> repeatedly will always return the
538 first record from the resultset.
539
540 =cut
541
542 sub next {
543   my ($self) = @_;
544   if (@{$self->{all_cache} || []}) {
545     $self->{all_cache_position} ||= 0;
546     return $self->{all_cache}->[$self->{all_cache_position}++];
547   }
548   if ($self->{attrs}{cache}) {
549     $self->{all_cache_position} = 1;
550     return ($self->all)[0];
551   }
552   my @row = (exists $self->{stashed_row} ?
553                @{delete $self->{stashed_row}} :
554                $self->cursor->next
555   );
556 #  warn Dumper(\@row); use Data::Dumper;
557   return unless (@row);
558   return $self->_construct_object(@row);
559 }
560
561 sub _construct_object {
562   my ($self, @row) = @_;
563   my @as = @{ $self->{attrs}{as} };
564   
565   my $info = $self->_collapse_result(\@as, \@row);
566   
567   my $new = $self->result_class->inflate_result($self->result_source, @$info);
568   
569   $new = $self->{attrs}{record_filter}->($new)
570     if exists $self->{attrs}{record_filter};
571   return $new;
572 }
573
574 sub _collapse_result {
575   my ($self, $as, $row, $prefix) = @_;
576
577   my %const;
578
579   my @copy = @$row;
580   foreach my $this_as (@$as) {
581     my $val = shift @copy;
582     if (defined $prefix) {
583       if ($this_as =~ m/^\Q${prefix}.\E(.+)$/) {
584         my $remain = $1;
585         $remain =~ /^(?:(.*)\.)?([^.]+)$/;
586         $const{$1||''}{$2} = $val;
587       }
588     } else {
589       $this_as =~ /^(?:(.*)\.)?([^.]+)$/;
590       $const{$1||''}{$2} = $val;
591     }
592   }
593
594   my $info = [ {}, {} ];
595   foreach my $key (keys %const) {
596     if (length $key) {
597       my $target = $info;
598       my @parts = split(/\./, $key);
599       foreach my $p (@parts) {
600         $target = $target->[1]->{$p} ||= [];
601       }
602       $target->[0] = $const{$key};
603     } else {
604       $info->[0] = $const{$key};
605     }
606   }
607
608   my @collapse;
609   if (defined $prefix) {
610     @collapse = map {
611         m/^\Q${prefix}.\E(.+)$/ ? ($1) : ()
612     } keys %{$self->{collapse}}
613   } else {
614     @collapse = keys %{$self->{collapse}};
615   };
616
617   if (@collapse) {
618     my ($c) = sort { length $a <=> length $b } @collapse;
619     my $target = $info;
620     foreach my $p (split(/\./, $c)) {
621       $target = $target->[1]->{$p} ||= [];
622     }
623     my $c_prefix = (defined($prefix) ? "${prefix}.${c}" : $c);
624     my @co_key = @{$self->{collapse}{$c_prefix}};
625     my %co_check = map { ($_, $target->[0]->{$_}); } @co_key;
626     my $tree = $self->_collapse_result($as, $row, $c_prefix);
627     my (@final, @raw);
628     while ( !(grep {
629                 !defined($tree->[0]->{$_}) ||
630                 $co_check{$_} ne $tree->[0]->{$_}
631               } @co_key) ) {
632       push(@final, $tree);
633       last unless (@raw = $self->cursor->next);
634       $row = $self->{stashed_row} = \@raw;
635       $tree = $self->_collapse_result($as, $row, $c_prefix);
636       #warn Data::Dumper::Dumper($tree, $row);
637     }
638     @$target = @final;
639   }
640
641   return $info;
642 }
643
644 =head2 result_source
645
646 =over 4
647
648 =item Arguments: $result_source?
649
650 =item Return Value: $result_source
651
652 =back
653
654 An accessor for the primary ResultSource object from which this ResultSet
655 is derived.
656
657 =cut
658
659
660 =head2 count
661
662 =over 4
663
664 =item Arguments: $cond, \%attrs??
665
666 =item Return Value: $count
667
668 =back
669
670 Performs an SQL C<COUNT> with the same query as the resultset was built
671 with to find the number of elements. If passed arguments, does a search
672 on the resultset and counts the results of that.
673
674 Note: When using C<count> with C<group_by>, L<DBIX::Class> emulates C<GROUP BY>
675 using C<COUNT( DISTINCT( columns ) )>. Some databases (notably SQLite) do
676 not support C<DISTINCT> with multiple columns. If you are using such a
677 database, you should only use columns from the main table in your C<group_by>
678 clause.
679
680 =cut
681
682 sub count {
683   my $self = shift;
684   return $self->search(@_)->count if @_ and defined $_[0];
685   return scalar @{ $self->get_cache } if @{ $self->get_cache };
686
687   my $count = $self->_count;
688   return 0 unless $count;
689
690   $count -= $self->{attrs}{offset} if $self->{attrs}{offset};
691   $count = $self->{attrs}{rows} if
692     $self->{attrs}{rows} and $self->{attrs}{rows} < $count;
693   return $count;
694 }
695
696 sub _count { # Separated out so pager can get the full count
697   my $self = shift;
698   my $select = { count => '*' };
699   my $attrs = { %{ $self->{attrs} } };
700   if (my $group_by = delete $attrs->{group_by}) {
701     delete $attrs->{having};
702     my @distinct = (ref $group_by ?  @$group_by : ($group_by));
703     # todo: try CONCAT for multi-column pk
704     my @pk = $self->result_source->primary_columns;
705     if (@pk == 1) {
706       foreach my $column (@distinct) {
707         if ($column =~ qr/^(?:\Q$attrs->{alias}.\E)?$pk[0]$/) {
708           @distinct = ($column);
709           last;
710         }
711       }
712     }
713
714     $select = { count => { distinct => \@distinct } };
715     #use Data::Dumper; die Dumper $select;
716   }
717
718   $attrs->{select} = $select;
719   $attrs->{as} = [qw/count/];
720
721   # offset, order by and page are not needed to count. record_filter is cdbi
722   delete $attrs->{$_} for qw/rows offset order_by page pager record_filter/;
723         
724   my ($count) = (ref $self)->new($self->result_source, $attrs)->cursor->next;
725   return $count;
726 }
727
728 =head2 count_literal
729
730 =over 4
731
732 =item Arguments: $sql_fragment, @bind_values
733
734 =item Return Value: $count
735
736 =back
737
738 Counts the results in a literal query. Equivalent to calling L</search_literal>
739 with the passed arguments, then L</count>.
740
741 =cut
742
743 sub count_literal { shift->search_literal(@_)->count; }
744
745 =head2 all
746
747 =over 4
748
749 =item Arguments: none
750
751 =item Return Value: @objects
752
753 =back
754
755 Returns all elements in the resultset. Called implicitly if the resultset
756 is returned in list context.
757
758 =cut
759
760 sub all {
761   my ($self) = @_;
762   return @{ $self->get_cache } if @{ $self->get_cache };
763
764   my @obj;
765
766   if (keys %{$self->{collapse}}) {
767       # Using $self->cursor->all is really just an optimisation.
768       # If we're collapsing has_many prefetches it probably makes
769       # very little difference, and this is cleaner than hacking
770       # _construct_object to survive the approach
771     $self->cursor->reset;
772     my @row = $self->cursor->next;
773     while (@row) {
774       push(@obj, $self->_construct_object(@row));
775       @row = (exists $self->{stashed_row}
776                ? @{delete $self->{stashed_row}}
777                : $self->cursor->next);
778     }
779   } else {
780     @obj = map { $self->_construct_object(@$_) } $self->cursor->all;
781   }
782
783   $self->set_cache(\@obj) if $self->{attrs}{cache};
784   return @obj;
785 }
786
787 =head2 reset
788
789 =over 4
790
791 =item Arguments: none
792
793 =item Return Value: $self
794
795 =back
796
797 Resets the resultset's cursor, so you can iterate through the elements again.
798
799 =cut
800
801 sub reset {
802   my ($self) = @_;
803   $self->{all_cache_position} = 0;
804   $self->cursor->reset;
805   return $self;
806 }
807
808 =head2 first
809
810 =over 4
811
812 =item Arguments: none
813
814 =item Return Value: $object?
815
816 =back
817
818 Resets the resultset and returns an object for the first result (if the
819 resultset returns anything).
820
821 =cut
822
823 sub first {
824   return $_[0]->reset->next;
825 }
826
827 # _cond_for_update_delete
828 #
829 # update/delete require the condition to be modified to handle
830 # the differing SQL syntax available.  This transforms the $self->{cond}
831 # appropriately, returning the new condition.
832
833 sub _cond_for_update_delete {
834   my ($self) = @_;
835   my $cond = {};
836
837   if (!ref($self->{cond})) {
838     # No-op. No condition, we're updating/deleting everything
839   }
840   elsif (ref $self->{cond} eq 'ARRAY') {
841     $cond = [
842       map {
843         my %hash;
844         foreach my $key (keys %{$_}) {
845           $key =~ /([^.]+)$/;
846           $hash{$1} = $_->{$key};
847         }
848         \%hash;
849       } @{$self->{cond}}
850     ];
851   }
852   elsif (ref $self->{cond} eq 'HASH') {
853     if ((keys %{$self->{cond}})[0] eq '-and') {
854       $cond->{-and} = [];
855
856       my @cond = @{$self->{cond}{-and}};
857       for (my $i = 0; $i < @cond - 1; $i++) {
858         my $entry = $cond[$i];
859
860         my %hash;
861         if (ref $entry eq 'HASH') {
862           foreach my $key (keys %{$entry}) {
863             $key =~ /([^.]+)$/;
864             $hash{$1} = $entry->{$key};
865           }
866         }
867         else {
868           $entry =~ /([^.]+)$/;
869           $hash{$entry} = $cond[++$i];
870         }
871
872         push @{$cond->{-and}}, \%hash;
873       }
874     }
875     else {
876       foreach my $key (keys %{$self->{cond}}) {
877         $key =~ /([^.]+)$/;
878         $cond->{$1} = $self->{cond}{$key};
879       }
880     }
881   }
882   else {
883     $self->throw_exception(
884       "Can't update/delete on resultset with condition unless hash or array"
885     );
886   }
887
888   return $cond;
889 }
890
891
892 =head2 update
893
894 =over 4
895
896 =item Arguments: \%values
897
898 =item Return Value: $storage_rv
899
900 =back
901
902 Sets the specified columns in the resultset to the supplied values in a
903 single query. Return value will be true if the update succeeded or false
904 if no records were updated; exact type of success value is storage-dependent.
905
906 =cut
907
908 sub update {
909   my ($self, $values) = @_;
910   $self->throw_exception("Values for update must be a hash")
911     unless ref $values eq 'HASH';
912
913   my $cond = $self->_cond_for_update_delete;
914
915   return $self->result_source->storage->update(
916     $self->result_source->from, $values, $cond
917   );
918 }
919
920 =head2 update_all
921
922 =over 4
923
924 =item Arguments: \%values
925
926 =item Return Value: 1
927
928 =back
929
930 Fetches all objects and updates them one at a time. Note that C<update_all>
931 will run DBIC cascade triggers, while L</update> will not.
932
933 =cut
934
935 sub update_all {
936   my ($self, $values) = @_;
937   $self->throw_exception("Values for update must be a hash")
938     unless ref $values eq 'HASH';
939   foreach my $obj ($self->all) {
940     $obj->set_columns($values)->update;
941   }
942   return 1;
943 }
944
945 =head2 delete
946
947 =over 4
948
949 =item Arguments: none
950
951 =item Return Value: 1
952
953 =back
954
955 Deletes the contents of the resultset from its result source. Note that this
956 will not run DBIC cascade triggers. See L</delete_all> if you need triggers
957 to run.
958
959 =cut
960
961 sub delete {
962   my ($self) = @_;
963   my $del = {};
964
965   my $cond = $self->_cond_for_update_delete;
966
967   $self->result_source->storage->delete($self->result_source->from, $cond);
968   return 1;
969 }
970
971 =head2 delete_all
972
973 =over 4
974
975 =item Arguments: none
976
977 =item Return Value: 1
978
979 =back
980
981 Fetches all objects and deletes them one at a time. Note that C<delete_all>
982 will run DBIC cascade triggers, while L</delete> will not.
983
984 =cut
985
986 sub delete_all {
987   my ($self) = @_;
988   $_->delete for $self->all;
989   return 1;
990 }
991
992 =head2 pager
993
994 =over 4
995
996 =item Arguments: none
997
998 =item Return Value: $pager
999
1000 =back
1001
1002 Return Value a L<Data::Page> object for the current resultset. Only makes
1003 sense for queries with a C<page> attribute.
1004
1005 =cut
1006
1007 sub pager {
1008   my ($self) = @_;
1009   my $attrs = $self->{attrs};
1010   $self->throw_exception("Can't create pager for non-paged rs")
1011     unless $self->{page};
1012   $attrs->{rows} ||= 10;
1013   return $self->{pager} ||= Data::Page->new(
1014     $self->_count, $attrs->{rows}, $self->{page});
1015 }
1016
1017 =head2 page
1018
1019 =over 4
1020
1021 =item Arguments: $page_number
1022
1023 =item Return Value: $rs
1024
1025 =back
1026
1027 Returns a resultset for the $page_number page of the resultset on which page
1028 is called, where each page contains a number of rows equal to the 'rows'
1029 attribute set on the resultset (10 by default).
1030
1031 =cut
1032
1033 sub page {
1034   my ($self, $page) = @_;
1035   my $attrs = { %{$self->{attrs}} };
1036   $attrs->{page} = $page;
1037   return (ref $self)->new($self->result_source, $attrs);
1038 }
1039
1040 =head2 new_result
1041
1042 =over 4
1043
1044 =item Arguments: \%vals
1045
1046 =item Return Value: $object
1047
1048 =back
1049
1050 Creates an object in the resultset's result class and returns it.
1051
1052 =cut
1053
1054 sub new_result {
1055   my ($self, $values) = @_;
1056   $self->throw_exception( "new_result needs a hash" )
1057     unless (ref $values eq 'HASH');
1058   $self->throw_exception(
1059     "Can't abstract implicit construct, condition not a hash"
1060   ) if ($self->{cond} && !(ref $self->{cond} eq 'HASH'));
1061   my %new = %$values;
1062   my $alias = $self->{attrs}{alias};
1063   foreach my $key (keys %{$self->{cond}||{}}) {
1064     $new{$1} = $self->{cond}{$key} if ($key =~ m/^(?:\Q${alias}.\E)?([^.]+)$/);
1065   }
1066   my $obj = $self->result_class->new(\%new);
1067   $obj->result_source($self->result_source) if $obj->can('result_source');
1068   return $obj;
1069 }
1070
1071 =head2 create
1072
1073 =over 4
1074
1075 =item Arguments: \%vals
1076
1077 =item Return Value: $object
1078
1079 =back
1080
1081 Inserts a record into the resultset and returns the object representing it.
1082
1083 Effectively a shortcut for C<< ->new_result(\%vals)->insert >>.
1084
1085 =cut
1086
1087 sub create {
1088   my ($self, $attrs) = @_;
1089   $self->throw_exception( "create needs a hashref" )
1090     unless ref $attrs eq 'HASH';
1091   return $self->new_result($attrs)->insert;
1092 }
1093
1094 =head2 find_or_create
1095
1096 =over 4
1097
1098 =item Arguments: \%vals, \%attrs?
1099
1100 =item Return Value: $object
1101
1102 =back
1103
1104   $class->find_or_create({ key => $val, ... });
1105
1106 Searches for a record matching the search condition; if it doesn't find one,
1107 creates one and returns that instead.
1108
1109   my $cd = $schema->resultset('CD')->find_or_create({
1110     cdid   => 5,
1111     artist => 'Massive Attack',
1112     title  => 'Mezzanine',
1113     year   => 2005,
1114   });
1115
1116 Also takes an optional C<key> attribute, to search by a specific key or unique
1117 constraint. For example:
1118
1119   my $cd = $schema->resultset('CD')->find_or_create(
1120     {
1121       artist => 'Massive Attack',
1122       title  => 'Mezzanine',
1123     },
1124     { key => 'artist_title' }
1125   );
1126
1127 See also L</find> and L</update_or_create>.
1128
1129 =cut
1130
1131 sub find_or_create {
1132   my $self     = shift;
1133   my $attrs    = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
1134   my $hash     = ref $_[0] eq 'HASH' ? shift : {@_};
1135   my $exists   = $self->find($hash, $attrs);
1136   return defined $exists ? $exists : $self->create($hash);
1137 }
1138
1139 =head2 update_or_create
1140
1141 =over 4
1142
1143 =item Arguments: \%col_values, { key => $unique_constraint }?
1144
1145 =item Return Value: $object
1146
1147 =back
1148
1149   $class->update_or_create({ col => $val, ... });
1150
1151 First, searches for an existing row matching one of the unique constraints
1152 (including the primary key) on the source of this resultset. If a row is
1153 found, updates it with the other given column values. Otherwise, creates a new
1154 row.
1155
1156 Takes an optional C<key> attribute to search on a specific unique constraint.
1157 For example:
1158
1159   # In your application
1160   my $cd = $schema->resultset('CD')->update_or_create(
1161     {
1162       artist => 'Massive Attack',
1163       title  => 'Mezzanine',
1164       year   => 1998,
1165     },
1166     { key => 'artist_title' }
1167   );
1168
1169 If no C<key> is specified, it searches on all unique constraints defined on the
1170 source, including the primary key.
1171
1172 If the C<key> is specified as C<primary>, it searches only on the primary key.
1173
1174 See also L</find> and L</find_or_create>.
1175
1176 =cut
1177
1178 sub update_or_create {
1179   my $self = shift;
1180   my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
1181   my $hash = ref $_[0] eq 'HASH' ? shift : {@_};
1182
1183   my $row = $self->find($hash, $attrs);
1184   if (defined $row) {
1185     $row->set_columns($hash);
1186     $row->update;
1187     return $row;
1188   }
1189
1190   return $self->create($hash);
1191 }
1192
1193 =head2 get_cache
1194
1195 =over 4
1196
1197 =item Arguments: none
1198
1199 =item Return Value: \@cache_objects?
1200
1201 =back
1202
1203 Gets the contents of the cache for the resultset, if the cache is set.
1204
1205 =cut
1206
1207 sub get_cache {
1208   shift->{all_cache} || [];
1209 }
1210
1211 =head2 set_cache
1212
1213 =over 4
1214
1215 =item Arguments: \@cache_objects
1216
1217 =item Return Value: \@cache_objects
1218
1219 =back
1220
1221 Sets the contents of the cache for the resultset. Expects an arrayref
1222 of objects of the same class as those produced by the resultset. Note that
1223 if the cache is set the resultset will return the cached objects rather
1224 than re-querying the database even if the cache attr is not set.
1225
1226 =cut
1227
1228 sub set_cache {
1229   my ( $self, $data ) = @_;
1230   $self->throw_exception("set_cache requires an arrayref")
1231     if ref $data ne 'ARRAY';
1232   my $result_class = $self->result_class;
1233   foreach( @$data ) {
1234     $self->throw_exception(
1235       "cannot cache object of type '$_', expected '$result_class'"
1236     ) if ref $_ ne $result_class;
1237   }
1238   $self->{all_cache} = $data;
1239 }
1240
1241 =head2 clear_cache
1242
1243 =over 4
1244
1245 =item Arguments: none
1246
1247 =item Return Value: []
1248
1249 =back
1250
1251 Clears the cache for the resultset.
1252
1253 =cut
1254
1255 sub clear_cache {
1256   shift->set_cache([]);
1257 }
1258
1259 =head2 related_resultset
1260
1261 =over 4
1262
1263 =item Arguments: $relationship_name
1264
1265 =item Return Value: $resultset
1266
1267 =back
1268
1269 Returns a related resultset for the supplied relationship name.
1270
1271   $artist_rs = $schema->resultset('CD')->related_resultset('Artist');
1272
1273 =cut
1274
1275 sub related_resultset {
1276   my ( $self, $rel ) = @_;
1277   $self->{related_resultsets} ||= {};
1278   return $self->{related_resultsets}{$rel} ||= do {
1279       #warn "fetching related resultset for rel '$rel'";
1280       my $rel_obj = $self->result_source->relationship_info($rel);
1281       $self->throw_exception(
1282         "search_related: result source '" . $self->result_source->name .
1283         "' has no such relationship ${rel}")
1284         unless $rel_obj; #die Dumper $self->{attrs};
1285
1286       my $rs = $self->search(undef, { join => $rel });
1287       my $alias = defined $rs->{attrs}{seen_join}{$rel}
1288                     && $rs->{attrs}{seen_join}{$rel} > 1
1289                   ? join('_', $rel, $rs->{attrs}{seen_join}{$rel})
1290                   : $rel;
1291
1292       $self->result_source->schema->resultset($rel_obj->{class}
1293            )->search( undef,
1294              { %{$rs->{attrs}},
1295                alias => $alias,
1296                select => undef,
1297                as => undef }
1298            );
1299   };
1300 }
1301
1302 =head2 throw_exception
1303
1304 See L<DBIx::Class::Schema/throw_exception> for details.
1305
1306 =cut
1307
1308 sub throw_exception {
1309   my $self=shift;
1310   $self->result_source->schema->throw_exception(@_);
1311 }
1312
1313 # XXX: FIXME: Attributes docs need clearing up
1314
1315 =head1 ATTRIBUTES
1316
1317 The resultset takes various attributes that modify its behavior. Here's an
1318 overview of them:
1319
1320 =head2 order_by
1321
1322 =over 4
1323
1324 =item Value: ($order_by | \@order_by)
1325
1326 =back
1327
1328 Which column(s) to order the results by. This is currently passed
1329 through directly to SQL, so you can give e.g. C<year DESC> for a
1330 descending order on the column `year'.
1331
1332 =head2 columns
1333
1334 =over 4
1335
1336 =item Value: \@columns
1337
1338 =back
1339
1340 Shortcut to request a particular set of columns to be retrieved.  Adds
1341 C<me.> onto the start of any column without a C<.> in it and sets C<select>
1342 from that, then auto-populates C<as> from C<select> as normal. (You may also
1343 use the C<cols> attribute, as in earlier versions of DBIC.)
1344
1345 =head2 include_columns
1346
1347 =over 4
1348
1349 =item Value: \@columns
1350
1351 =back
1352
1353 Shortcut to include additional columns in the returned results - for example
1354
1355   $schema->resultset('CD')->search(undef, {
1356     include_columns => ['artist.name'],
1357     join => ['artist']
1358   });
1359
1360 would return all CDs and include a 'name' column to the information
1361 passed to object inflation
1362
1363 =head2 select
1364
1365 =over 4
1366
1367 =item Value: \@select_columns
1368
1369 =back
1370
1371 Indicates which columns should be selected from the storage. You can use
1372 column names, or in the case of RDBMS back ends, function or stored procedure
1373 names:
1374
1375   $rs = $schema->resultset('Employee')->search(undef, {
1376     select => [
1377       'name',
1378       { count => 'employeeid' },
1379       { sum => 'salary' }
1380     ]
1381   });
1382
1383 When you use function/stored procedure names and do not supply an C<as>
1384 attribute, the column names returned are storage-dependent. E.g. MySQL would
1385 return a column named C<count(employeeid)> in the above example.
1386
1387 =head2 as
1388
1389 =over 4
1390
1391 =item Value: \@inflation_names
1392
1393 =back
1394
1395 Indicates column names for object inflation. This is used in conjunction with
1396 C<select>, usually when C<select> contains one or more function or stored
1397 procedure names:
1398
1399   $rs = $schema->resultset('Employee')->search(undef, {
1400     select => [
1401       'name',
1402       { count => 'employeeid' }
1403     ],
1404     as => ['name', 'employee_count'],
1405   });
1406
1407   my $employee = $rs->first(); # get the first Employee
1408
1409 If the object against which the search is performed already has an accessor
1410 matching a column name specified in C<as>, the value can be retrieved using
1411 the accessor as normal:
1412
1413   my $name = $employee->name();
1414
1415 If on the other hand an accessor does not exist in the object, you need to
1416 use C<get_column> instead:
1417
1418   my $employee_count = $employee->get_column('employee_count');
1419
1420 You can create your own accessors if required - see
1421 L<DBIx::Class::Manual::Cookbook> for details.
1422
1423 =head2 join
1424
1425 =over 4
1426
1427 =item Value: ($rel_name | \@rel_names | \%rel_names)
1428
1429 =back
1430
1431 Contains a list of relationships that should be joined for this query.  For
1432 example:
1433
1434   # Get CDs by Nine Inch Nails
1435   my $rs = $schema->resultset('CD')->search(
1436     { 'artist.name' => 'Nine Inch Nails' },
1437     { join => 'artist' }
1438   );
1439
1440 Can also contain a hash reference to refer to the other relation's relations.
1441 For example:
1442
1443   package MyApp::Schema::Track;
1444   use base qw/DBIx::Class/;
1445   __PACKAGE__->table('track');
1446   __PACKAGE__->add_columns(qw/trackid cd position title/);
1447   __PACKAGE__->set_primary_key('trackid');
1448   __PACKAGE__->belongs_to(cd => 'MyApp::Schema::CD');
1449   1;
1450
1451   # In your application
1452   my $rs = $schema->resultset('Artist')->search(
1453     { 'track.title' => 'Teardrop' },
1454     {
1455       join     => { cd => 'track' },
1456       order_by => 'artist.name',
1457     }
1458   );
1459
1460 If the same join is supplied twice, it will be aliased to <rel>_2 (and
1461 similarly for a third time). For e.g.
1462
1463   my $rs = $schema->resultset('Artist')->search({
1464     'cds.title'   => 'Down to Earth',
1465     'cds_2.title' => 'Popular',
1466   }, {
1467     join => [ qw/cds cds/ ],
1468   });
1469
1470 will return a set of all artists that have both a cd with title 'Down
1471 to Earth' and a cd with title 'Popular'.
1472
1473 If you want to fetch related objects from other tables as well, see C<prefetch>
1474 below.
1475
1476 =head2 prefetch
1477
1478 =over 4
1479
1480 =item Value: ($rel_name | \@rel_names | \%rel_names)
1481
1482 =back
1483
1484 Contains one or more relationships that should be fetched along with the main
1485 query (when they are accessed afterwards they will have already been
1486 "prefetched").  This is useful for when you know you will need the related
1487 objects, because it saves at least one query:
1488
1489   my $rs = $schema->resultset('Tag')->search(
1490     undef,
1491     {
1492       prefetch => {
1493         cd => 'artist'
1494       }
1495     }
1496   );
1497
1498 The initial search results in SQL like the following:
1499
1500   SELECT tag.*, cd.*, artist.* FROM tag
1501   JOIN cd ON tag.cd = cd.cdid
1502   JOIN artist ON cd.artist = artist.artistid
1503
1504 L<DBIx::Class> has no need to go back to the database when we access the
1505 C<cd> or C<artist> relationships, which saves us two SQL statements in this
1506 case.
1507
1508 Simple prefetches will be joined automatically, so there is no need
1509 for a C<join> attribute in the above search. If you're prefetching to
1510 depth (e.g. { cd => { artist => 'label' } or similar), you'll need to
1511 specify the join as well.
1512
1513 C<prefetch> can be used with the following relationship types: C<belongs_to>,
1514 C<has_one> (or if you're using C<add_relationship>, any relationship declared
1515 with an accessor type of 'single' or 'filter').
1516
1517 =head2 from
1518
1519 =over 4
1520
1521 =item Value: \@from_clause
1522
1523 =back
1524
1525 The C<from> attribute gives you manual control over the C<FROM> clause of SQL
1526 statements generated by L<DBIx::Class>, allowing you to express custom C<JOIN>
1527 clauses.
1528
1529 NOTE: Use this on your own risk.  This allows you to shoot off your foot!
1530 C<join> will usually do what you need and it is strongly recommended that you
1531 avoid using C<from> unless you cannot achieve the desired result using C<join>.
1532
1533 In simple terms, C<from> works as follows:
1534
1535     [
1536         { <alias> => <table>, -join_type => 'inner|left|right' }
1537         [] # nested JOIN (optional)
1538         { <table.column> => <foreign_table.foreign_key> }
1539     ]
1540
1541     JOIN
1542         <alias> <table>
1543         [JOIN ...]
1544     ON <table.column> = <foreign_table.foreign_key>
1545
1546 An easy way to follow the examples below is to remember the following:
1547
1548     Anything inside "[]" is a JOIN
1549     Anything inside "{}" is a condition for the enclosing JOIN
1550
1551 The following examples utilize a "person" table in a family tree application.
1552 In order to express parent->child relationships, this table is self-joined:
1553
1554     # Person->belongs_to('father' => 'Person');
1555     # Person->belongs_to('mother' => 'Person');
1556
1557 C<from> can be used to nest joins. Here we return all children with a father,
1558 then search against all mothers of those children:
1559
1560   $rs = $schema->resultset('Person')->search(
1561       undef,
1562       {
1563           alias => 'mother', # alias columns in accordance with "from"
1564           from => [
1565               { mother => 'person' },
1566               [
1567                   [
1568                       { child => 'person' },
1569                       [
1570                           { father => 'person' },
1571                           { 'father.person_id' => 'child.father_id' }
1572                       ]
1573                   ],
1574                   { 'mother.person_id' => 'child.mother_id' }
1575               ],
1576           ]
1577       },
1578   );
1579
1580   # Equivalent SQL:
1581   # SELECT mother.* FROM person mother
1582   # JOIN (
1583   #   person child
1584   #   JOIN person father
1585   #   ON ( father.person_id = child.father_id )
1586   # )
1587   # ON ( mother.person_id = child.mother_id )
1588
1589 The type of any join can be controlled manually. To search against only people
1590 with a father in the person table, we could explicitly use C<INNER JOIN>:
1591
1592     $rs = $schema->resultset('Person')->search(
1593         undef,
1594         {
1595             alias => 'child', # alias columns in accordance with "from"
1596             from => [
1597                 { child => 'person' },
1598                 [
1599                     { father => 'person', -join_type => 'inner' },
1600                     { 'father.id' => 'child.father_id' }
1601                 ],
1602             ]
1603         },
1604     );
1605
1606     # Equivalent SQL:
1607     # SELECT child.* FROM person child
1608     # INNER JOIN person father ON child.father_id = father.id
1609
1610 =head2 page
1611
1612 =over 4
1613
1614 =item Value: $page
1615
1616 =back
1617
1618 Makes the resultset paged and specifies the page to retrieve. Effectively
1619 identical to creating a non-pages resultset and then calling ->page($page)
1620 on it.
1621
1622 =head2 rows
1623
1624 =over 4
1625
1626 =item Value: $rows
1627
1628 =back
1629
1630 Specifes the maximum number of rows for direct retrieval or the number of
1631 rows per page if the page attribute or method is used.
1632
1633 =head2 group_by
1634
1635 =over 4
1636
1637 =item Value: \@columns
1638
1639 =back
1640
1641 A arrayref of columns to group by. Can include columns of joined tables.
1642
1643   group_by => [qw/ column1 column2 ... /]
1644
1645 =head2 having
1646
1647 =over 4
1648
1649 =item Value: $condition
1650
1651 =back
1652
1653 HAVING is a select statement attribute that is applied between GROUP BY and
1654 ORDER BY. It is applied to the after the grouping calculations have been
1655 done. 
1656
1657   having => { 'count(employee)' => { '>=', 100 } }
1658
1659 =head2 distinct
1660
1661 =over 4
1662
1663 =item Value: (0 | 1)
1664
1665 =back
1666
1667 Set to 1 to group by all columns.
1668
1669 =head2 cache
1670
1671 Set to 1 to cache search results. This prevents extra SQL queries if you
1672 revisit rows in your ResultSet:
1673
1674   my $resultset = $schema->resultset('Artist')->search( undef, { cache => 1 } );
1675   
1676   while( my $artist = $resultset->next ) {
1677     ... do stuff ...
1678   }
1679
1680   $rs->first; # without cache, this would issue a query
1681
1682 By default, searches are not cached.
1683
1684 For more examples of using these attributes, see
1685 L<DBIx::Class::Manual::Cookbook>.
1686
1687 =cut
1688
1689 1;