Fix busted get_column when using +select (and friends)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSetColumn.pm
1 package DBIx::Class::ResultSetColumn;
2 use strict;
3 use warnings;
4 use base 'DBIx::Class';
5 use List::Util;
6
7 =head1 NAME
8
9   DBIx::Class::ResultSetColumn - helpful methods for messing
10   with a single column of the resultset
11
12 =head1 SYNOPSIS
13
14   $rs = $schema->resultset('CD')->search({ artist => 'Tool' });
15   $rs_column = $rs->get_column('year');
16   $max_year = $rs_column->max; #returns latest year
17
18 =head1 DESCRIPTION
19
20 A convenience class used to perform operations on a specific column of
21 a resultset.
22
23 =cut
24
25 =head1 METHODS
26
27 =head2 new
28
29   my $obj = DBIx::Class::ResultSetColumn->new($rs, $column);
30
31 Creates a new resultset column object from the resultset and column
32 passed as params. Used internally by L<DBIx::Class::ResultSet/get_column>.
33
34 =cut
35
36 sub new {
37   my ($class, $rs, $column) = @_;
38   $class = ref $class if ref $class;
39   my $new_parent_rs = $rs->search_rs; # we don't want to mess up the original, so clone it
40   my $attrs = $new_parent_rs->_resolved_attrs;
41   $new_parent_rs->{attrs}->{prefetch} = undef; # prefetch cause additional columns to be fetched
42
43   # If $column can be found in the 'as' list of the parent resultset, use the
44   # corresponding element of its 'select' list (to keep any custom column
45   # definition set up with 'select' or '+select' attrs), otherwise use $column
46   # (to create a new column definition on-the-fly).
47   my $as_list = $attrs->{as} || [];
48   my $select_list = $attrs->{select} || [];
49   my $as_index = List::Util::first { ($as_list->[$_] || "") eq $column } 0..$#$as_list;
50   my $select = defined $as_index ? $select_list->[$as_index] : $column;
51
52   my $new = bless { _select => $select, _as => $column, _parent_resultset => $new_parent_rs }, $class;
53   $new->throw_exception("column must be supplied") unless $column;
54   return $new;
55 }
56
57 =head2 as_query (EXPERIMENTAL)
58
59 =over 4
60
61 =item Arguments: none
62
63 =item Return Value: \[ $sql, @bind ]
64
65 =back
66
67 Returns the SQL query and bind vars associated with the invocant.
68
69 This is generally used as the RHS for a subquery.
70
71 B<NOTE>: This feature is still experimental.
72
73 =cut
74
75 sub as_query { return shift->_resultset->as_query }
76
77 =head2 next
78
79 =over 4
80
81 =item Arguments: none
82
83 =item Return Value: $value
84
85 =back
86
87 Returns the next value of the column in the resultset (or C<undef> if
88 there is none).
89
90 Much like L<DBIx::Class::ResultSet/next> but just returning the 
91 one value.
92
93 =cut
94
95 sub next {
96   my $self = shift;
97   my ($row) = $self->_resultset->cursor->next;
98   return $row;
99 }
100
101 =head2 all
102
103 =over 4
104
105 =item Arguments: none
106
107 =item Return Value: @values
108
109 =back
110
111 Returns all values of the column in the resultset (or C<undef> if
112 there are none).
113
114 Much like L<DBIx::Class::ResultSet/all> but returns values rather
115 than row objects.
116
117 =cut
118
119 sub all {
120   my $self = shift;
121   return map { $_->[0] } $self->_resultset->cursor->all;
122 }
123
124 =head2 reset
125
126 =over 4
127
128 =item Arguments: none
129
130 =item Return Value: $self
131
132 =back
133
134 Resets the underlying resultset's cursor, so you can iterate through the
135 elements of the column again.
136
137 Much like L<DBIx::Class::ResultSet/reset>.
138
139 =cut
140
141 sub reset {
142   my $self = shift;
143   $self->_resultset->cursor->reset;
144   return $self;
145 }
146
147 =head2 first
148
149 =over 4
150
151 =item Arguments: none
152
153 =item Return Value: $value
154
155 =back
156
157 Resets the underlying resultset and returns the next value of the column in the
158 resultset (or C<undef> if there is none).
159
160 Much like L<DBIx::Class::ResultSet/first> but just returning the one value.
161
162 =cut
163
164 sub first {
165   my $self = shift;
166   my ($row) = $self->_resultset->cursor->reset->next;
167   return $row;
168 }
169
170 =head2 min
171
172 =over 4
173
174 =item Arguments: none
175
176 =item Return Value: $lowest_value
177
178 =back
179
180   my $first_year = $year_col->min();
181
182 Wrapper for ->func. Returns the lowest value of the column in the
183 resultset (or C<undef> if there are none).
184
185 =cut
186
187 sub min {
188   return shift->func('MIN');
189 }
190
191 =head2 min_rs
192
193 =over 4
194
195 =item Arguments: none
196
197 =item Return Value: $resultset
198
199 =back
200
201   my $rs = $year_col->min_rs();
202
203 Wrapper for ->func_rs for function MIN().
204
205 =cut
206
207 sub min_rs { return shift->func_rs('MIN') }
208
209 =head2 max
210
211 =over 4
212
213 =item Arguments: none
214
215 =item Return Value: $highest_value
216
217 =back
218
219   my $last_year = $year_col->max();
220
221 Wrapper for ->func. Returns the highest value of the column in the
222 resultset (or C<undef> if there are none).
223
224 =cut
225
226 sub max {
227   return shift->func('MAX');
228 }
229
230 =head2 max_rs
231
232 =over 4
233
234 =item Arguments: none
235
236 =item Return Value: $resultset
237
238 =back
239
240   my $rs = $year_col->max_rs();
241
242 Wrapper for ->func_rs for function MAX().
243
244 =cut
245
246 sub max_rs { return shift->func_rs('MAX') }
247
248 =head2 sum
249
250 =over 4
251
252 =item Arguments: none
253
254 =item Return Value: $sum_of_values
255
256 =back
257
258   my $total = $prices_col->sum();
259
260 Wrapper for ->func. Returns the sum of all the values in the column of
261 the resultset. Use on varchar-like columns at your own risk.
262
263 =cut
264
265 sub sum {
266   return shift->func('SUM');
267 }
268
269 =head2 sum_rs
270
271 =over 4
272
273 =item Arguments: none
274
275 =item Return Value: $resultset
276
277 =back
278
279   my $rs = $year_col->sum_rs();
280
281 Wrapper for ->func_rs for function SUM().
282
283 =cut
284
285 sub sum_rs { return shift->func_rs('SUM') }
286
287 =head2 func
288
289 =over 4
290
291 =item Arguments: $function
292
293 =item Return Value: $function_return_value
294
295 =back
296
297   $rs = $schema->resultset("CD")->search({});
298   $length = $rs->get_column('title')->func('LENGTH');
299
300 Runs a query using the function on the column and returns the
301 value. Produces the following SQL:
302
303   SELECT LENGTH( title ) FROM cd me
304
305 =cut
306
307 sub func {
308   my ($self,$function) = @_;
309   my $cursor = $self->func_rs($function)->cursor;
310   
311   if( wantarray ) {
312     return map { $_->[ 0 ] } $cursor->all;
313   }
314
315   return ( $cursor->next )[ 0 ];
316 }
317
318 =head2 func_rs
319
320 =over 4
321
322 =item Arguments: $function
323
324 =item Return Value: $resultset
325
326 =back
327
328 Creates the resultset that C<func()> uses to run its query.
329
330 =cut
331
332 sub func_rs {
333   my ($self,$function) = @_;
334   return $self->{_parent_resultset}->search(
335     undef, {
336       select => {$function => $self->{_select}},
337       as => [$self->{_as}],
338     },
339   );
340 }
341
342 =head2 throw_exception
343
344 See L<DBIx::Class::Schema/throw_exception> for details.
345   
346 =cut 
347     
348 sub throw_exception {
349   my $self=shift;
350   if (ref $self && $self->{_parent_resultset}) {
351     $self->{_parent_resultset}->throw_exception(@_)
352   } else {
353     croak(@_);
354   }
355 }
356
357 # _resultset
358 #
359 # Arguments: none
360 #
361 # Return Value: $resultset
362 #
363 #  $year_col->_resultset->next
364 #
365 # Returns the underlying resultset. Creates it from the parent resultset if
366 # necessary.
367
368 sub _resultset {
369   my $self = shift;
370
371   return $self->{_resultset} ||= $self->{_parent_resultset}->search(undef,
372     {
373       select => [$self->{_select}],
374       as => [$self->{_as}]
375     }
376   );
377 }
378
379 1;
380
381 =head1 AUTHORS
382
383 Luke Saunders <luke.saunders@gmail.com>
384
385 Jess Robinson
386
387 =head1 LICENSE
388
389 You may distribute this code under the same terms as Perl itself.
390
391 =cut