The real fix
[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
40   $rs->throw_exception("column must be supplied") unless $column;
41
42   my $orig_attrs = $rs->_resolved_attrs;
43   my $new_parent_rs = $rs->search_rs;
44
45   # prefetch causes additional columns to be fetched, but we can not just make a new
46   # rs via the _resolved_attrs trick - we need to retain the separation between
47   # +select/+as and select/as. At the same time we want to preserve any joins that the
48   # prefetch would otherwise generate.
49
50   my $new_attrs = $new_parent_rs->{attrs} ||= {};
51   $new_attrs->{join} = $rs->_merge_attr( delete $new_attrs->{join}, delete $new_attrs->{prefetch} );
52
53   # If $column can be found in the 'as' list of the parent resultset, use the
54   # corresponding element of its 'select' list (to keep any custom column
55   # definition set up with 'select' or '+select' attrs), otherwise use $column
56   # (to create a new column definition on-the-fly).
57
58   my $as_list = $orig_attrs->{as} || [];
59   my $select_list = $orig_attrs->{select} || [];
60   my $as_index = List::Util::first { ($as_list->[$_] || "") eq $column } 0..$#$as_list;
61   my $select = defined $as_index ? $select_list->[$as_index] : $column;
62
63   # {collapse} would mean a has_many join was injected, which in turn means
64   # we need to group IF WE CAN (only if the column in question is unique)
65   if (!$new_attrs->{group_by} && keys %{$orig_attrs->{collapse}}) {
66
67     # scan for a constraint that would contain our column only - that'd be proof
68     # enough it is unique
69     my $constraints = { $rs->result_source->unique_constraints };
70     for my $constraint_columns ( values %$constraints ) {
71
72       next unless @$constraint_columns == 1;
73
74       my $col = $constraint_columns->[0];
75       my $fqcol = join ('.', $new_attrs->{alias}, $col);
76
77       if ($col eq $select or $fqcol eq $select) {
78         $new_attrs->{group_by} = [ $select ];
79         last;
80       }
81     }
82   }
83
84   my $new = bless { _select => $select, _as => $column, _parent_resultset => $new_parent_rs }, $class;
85   return $new;
86 }
87
88 =head2 as_query (EXPERIMENTAL)
89
90 =over 4
91
92 =item Arguments: none
93
94 =item Return Value: \[ $sql, @bind ]
95
96 =back
97
98 Returns the SQL query and bind vars associated with the invocant.
99
100 This is generally used as the RHS for a subquery.
101
102 B<NOTE>: This feature is still experimental.
103
104 =cut
105
106 sub as_query { return shift->_resultset->as_query(@_) }
107
108 =head2 next
109
110 =over 4
111
112 =item Arguments: none
113
114 =item Return Value: $value
115
116 =back
117
118 Returns the next value of the column in the resultset (or C<undef> if
119 there is none).
120
121 Much like L<DBIx::Class::ResultSet/next> but just returning the 
122 one value.
123
124 =cut
125
126 sub next {
127   my $self = shift;
128
129   # using cursor so we don't inflate anything
130   my ($row) = $self->_resultset->cursor->next;
131
132   return $row;
133 }
134
135 =head2 all
136
137 =over 4
138
139 =item Arguments: none
140
141 =item Return Value: @values
142
143 =back
144
145 Returns all values of the column in the resultset (or C<undef> if
146 there are none).
147
148 Much like L<DBIx::Class::ResultSet/all> but returns values rather
149 than row objects.
150
151 =cut
152
153 sub all {
154   my $self = shift;
155
156   # using cursor so we don't inflate anything
157   return map { $_->[0] } $self->_resultset->cursor->all;
158 }
159
160 =head2 reset
161
162 =over 4
163
164 =item Arguments: none
165
166 =item Return Value: $self
167
168 =back
169
170 Resets the underlying resultset's cursor, so you can iterate through the
171 elements of the column again.
172
173 Much like L<DBIx::Class::ResultSet/reset>.
174
175 =cut
176
177 sub reset {
178   my $self = shift;
179   $self->_resultset->cursor->reset;
180   return $self;
181 }
182
183 =head2 first
184
185 =over 4
186
187 =item Arguments: none
188
189 =item Return Value: $value
190
191 =back
192
193 Resets the underlying resultset and returns the next value of the column in the
194 resultset (or C<undef> if there is none).
195
196 Much like L<DBIx::Class::ResultSet/first> but just returning the one value.
197
198 =cut
199
200 sub first {
201   my $self = shift;
202
203   # using cursor so we don't inflate anything
204   $self->_resultset->cursor->reset;
205   my ($row) = $self->_resultset->cursor->next;
206
207   return $row;
208 }
209
210 =head2 min
211
212 =over 4
213
214 =item Arguments: none
215
216 =item Return Value: $lowest_value
217
218 =back
219
220   my $first_year = $year_col->min();
221
222 Wrapper for ->func. Returns the lowest value of the column in the
223 resultset (or C<undef> if there are none).
224
225 =cut
226
227 sub min {
228   return shift->func('MIN');
229 }
230
231 =head2 min_rs
232
233 =over 4
234
235 =item Arguments: none
236
237 =item Return Value: $resultset
238
239 =back
240
241   my $rs = $year_col->min_rs();
242
243 Wrapper for ->func_rs for function MIN().
244
245 =cut
246
247 sub min_rs { return shift->func_rs('MIN') }
248
249 =head2 max
250
251 =over 4
252
253 =item Arguments: none
254
255 =item Return Value: $highest_value
256
257 =back
258
259   my $last_year = $year_col->max();
260
261 Wrapper for ->func. Returns the highest value of the column in the
262 resultset (or C<undef> if there are none).
263
264 =cut
265
266 sub max {
267   return shift->func('MAX');
268 }
269
270 =head2 max_rs
271
272 =over 4
273
274 =item Arguments: none
275
276 =item Return Value: $resultset
277
278 =back
279
280   my $rs = $year_col->max_rs();
281
282 Wrapper for ->func_rs for function MAX().
283
284 =cut
285
286 sub max_rs { return shift->func_rs('MAX') }
287
288 =head2 sum
289
290 =over 4
291
292 =item Arguments: none
293
294 =item Return Value: $sum_of_values
295
296 =back
297
298   my $total = $prices_col->sum();
299
300 Wrapper for ->func. Returns the sum of all the values in the column of
301 the resultset. Use on varchar-like columns at your own risk.
302
303 =cut
304
305 sub sum {
306   return shift->func('SUM');
307 }
308
309 =head2 sum_rs
310
311 =over 4
312
313 =item Arguments: none
314
315 =item Return Value: $resultset
316
317 =back
318
319   my $rs = $year_col->sum_rs();
320
321 Wrapper for ->func_rs for function SUM().
322
323 =cut
324
325 sub sum_rs { return shift->func_rs('SUM') }
326
327 =head2 func
328
329 =over 4
330
331 =item Arguments: $function
332
333 =item Return Value: $function_return_value
334
335 =back
336
337   $rs = $schema->resultset("CD")->search({});
338   $length = $rs->get_column('title')->func('LENGTH');
339
340 Runs a query using the function on the column and returns the
341 value. Produces the following SQL:
342
343   SELECT LENGTH( title ) FROM cd me
344
345 =cut
346
347 sub func {
348   my ($self,$function) = @_;
349   my $cursor = $self->func_rs($function)->cursor;
350
351   if( wantarray ) {
352     return map { $_->[ 0 ] } $cursor->all;
353   }
354
355   return ( $cursor->next )[ 0 ];
356 }
357
358 =head2 func_rs
359
360 =over 4
361
362 =item Arguments: $function
363
364 =item Return Value: $resultset
365
366 =back
367
368 Creates the resultset that C<func()> uses to run its query.
369
370 =cut
371
372 sub func_rs {
373   my ($self,$function) = @_;
374   return $self->{_parent_resultset}->search(
375     undef, {
376       select => {$function => $self->{_select}},
377       as => [$self->{_as}],
378     },
379   );
380 }
381
382 =head2 throw_exception
383
384 See L<DBIx::Class::Schema/throw_exception> for details.
385
386 =cut 
387
388 sub throw_exception {
389   my $self=shift;
390   if (ref $self && $self->{_parent_resultset}) {
391     $self->{_parent_resultset}->throw_exception(@_)
392   } else {
393     croak(@_);
394   }
395 }
396
397 # _resultset
398 #
399 # Arguments: none
400 #
401 # Return Value: $resultset
402 #
403 #  $year_col->_resultset->next
404 #
405 # Returns the underlying resultset. Creates it from the parent resultset if
406 # necessary.
407 #
408 sub _resultset {
409   my $self = shift;
410
411   return $self->{_resultset} ||= $self->{_parent_resultset}->search(undef,
412     {
413       select => [$self->{_select}],
414       as => [$self->{_as}]
415     }
416   );
417 }
418
419 1;
420
421 =head1 AUTHORS
422
423 Luke Saunders <luke.saunders@gmail.com>
424
425 Jess Robinson
426
427 =head1 LICENSE
428
429 You may distribute this code under the same terms as Perl itself.
430
431 =cut