AUTHORS mass update; mst doesn't have to take credit for -everything- :)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSetColumn.pm
1 package DBIx::Class::ResultSetColumn;
2
3 use strict;
4 use warnings;
5
6 use base 'DBIx::Class';
7 use DBIx::Class::Carp;
8 use DBIx::Class::Exception;
9
10 # not importing first() as it will clash with our own method
11 use List::Util ();
12
13 =head1 NAME
14
15   DBIx::Class::ResultSetColumn - helpful methods for messing
16   with a single column of the resultset
17
18 =head1 SYNOPSIS
19
20   $rs = $schema->resultset('CD')->search({ artist => 'Tool' });
21   $rs_column = $rs->get_column('year');
22   $max_year = $rs_column->max; #returns latest year
23
24 =head1 DESCRIPTION
25
26 A convenience class used to perform operations on a specific column of
27 a resultset.
28
29 =cut
30
31 =head1 METHODS
32
33 =head2 new
34
35   my $obj = DBIx::Class::ResultSetColumn->new($rs, $column);
36
37 Creates a new resultset column object from the resultset and column
38 passed as params. Used internally by L<DBIx::Class::ResultSet/get_column>.
39
40 =cut
41
42 sub new {
43   my ($class, $rs, $column) = @_;
44   $class = ref $class if ref $class;
45
46   $rs->throw_exception('column must be supplied') unless $column;
47
48   my $orig_attrs = $rs->_resolved_attrs;
49   my $alias = $rs->current_source_alias;
50   my $rsrc = $rs->result_source;
51
52   # If $column can be found in the 'as' list of the parent resultset, use the
53   # corresponding element of its 'select' list (to keep any custom column
54   # definition set up with 'select' or '+select' attrs), otherwise use $column
55   # (to create a new column definition on-the-fly).
56   my $as_list = $orig_attrs->{as} || [];
57   my $select_list = $orig_attrs->{select} || [];
58   my $as_index = List::Util::first { ($as_list->[$_] || "") eq $column } 0..$#$as_list;
59   my $select = defined $as_index ? $select_list->[$as_index] : $column;
60
61   my ($new_parent_rs, $colmap);
62   for ($rsrc->columns, $column) {
63     if ($_ =~ /^ \Q$alias\E \. ([^\.]+) $ /x) {
64       $colmap->{$_} = $1;
65     }
66     elsif ($_ !~ /\./) {
67       $colmap->{"$alias.$_"} = $_;
68       $colmap->{$_} = $_;
69     }
70   }
71
72   # analyze the order_by, and see if it is done over a function/nonexistentcolumn
73   # if this is the case we will need to wrap a subquery since the result of RSC
74   # *must* be a single column select
75   if (
76     scalar grep
77       { ! exists $colmap->{$_->[0]} }
78       ( $rsrc->schema->storage->_extract_order_criteria ($orig_attrs->{order_by} ) )
79   ) {
80     # nuke the prefetch before collapsing to sql
81     my $subq_rs = $rs->search;
82     $subq_rs->{attrs}{join} = $subq_rs->_merge_joinpref_attr( $subq_rs->{attrs}{join}, delete $subq_rs->{attrs}{prefetch} );
83     $new_parent_rs = $subq_rs->as_subselect_rs;
84   }
85
86   $new_parent_rs ||= $rs->search_rs;
87   my $new_attrs = $new_parent_rs->{attrs} ||= {};
88
89   # prefetch causes additional columns to be fetched, but we can not just make a new
90   # rs via the _resolved_attrs trick - we need to retain the separation between
91   # +select/+as and select/as. At the same time we want to preserve any joins that the
92   # prefetch would otherwise generate.
93   $new_attrs->{join} = $rs->_merge_joinpref_attr( $new_attrs->{join}, delete $new_attrs->{prefetch} );
94
95   # {collapse} would mean a has_many join was injected, which in turn means
96   # we need to group *IF WE CAN* (only if the column in question is unique)
97   if (!$orig_attrs->{group_by} && keys %{$orig_attrs->{collapse}}) {
98
99     if ($colmap->{$select} and $rsrc->_identifying_column_set([$colmap->{$select}])) {
100       $new_attrs->{group_by} = [ $select ];
101       delete $new_attrs->{distinct}; # it is ignored when group_by is present
102     }
103     else {
104       carp (
105           "Attempting to retrieve non-unique column '$column' on a resultset containing "
106         . 'one-to-many joins will return duplicate results.'
107       );
108     }
109   }
110
111   my $new = bless { _select => $select, _as => $column, _parent_resultset => $new_parent_rs }, $class;
112   return $new;
113 }
114
115 =head2 as_query
116
117 =over 4
118
119 =item Arguments: none
120
121 =item Return Value: \[ $sql, @bind ]
122
123 =back
124
125 Returns the SQL query and bind vars associated with the invocant.
126
127 This is generally used as the RHS for a subquery.
128
129 =cut
130
131 sub as_query { return shift->_resultset->as_query(@_) }
132
133 =head2 next
134
135 =over 4
136
137 =item Arguments: none
138
139 =item Return Value: $value
140
141 =back
142
143 Returns the next value of the column in the resultset (or C<undef> if
144 there is none).
145
146 Much like L<DBIx::Class::ResultSet/next> but just returning the
147 one value.
148
149 =cut
150
151 sub next {
152   my $self = shift;
153
154   # using cursor so we don't inflate anything
155   my ($row) = $self->_resultset->cursor->next;
156
157   return $row;
158 }
159
160 =head2 all
161
162 =over 4
163
164 =item Arguments: none
165
166 =item Return Value: @values
167
168 =back
169
170 Returns all values of the column in the resultset (or C<undef> if
171 there are none).
172
173 Much like L<DBIx::Class::ResultSet/all> but returns values rather
174 than row objects.
175
176 =cut
177
178 sub all {
179   my $self = shift;
180
181   # using cursor so we don't inflate anything
182   return map { $_->[0] } $self->_resultset->cursor->all;
183 }
184
185 =head2 reset
186
187 =over 4
188
189 =item Arguments: none
190
191 =item Return Value: $self
192
193 =back
194
195 Resets the underlying resultset's cursor, so you can iterate through the
196 elements of the column again.
197
198 Much like L<DBIx::Class::ResultSet/reset>.
199
200 =cut
201
202 sub reset {
203   my $self = shift;
204   $self->_resultset->cursor->reset;
205   return $self;
206 }
207
208 =head2 first
209
210 =over 4
211
212 =item Arguments: none
213
214 =item Return Value: $value
215
216 =back
217
218 Resets the underlying resultset and returns the next value of the column in the
219 resultset (or C<undef> if there is none).
220
221 Much like L<DBIx::Class::ResultSet/first> but just returning the one value.
222
223 =cut
224
225 sub first {
226   my $self = shift;
227
228   # using cursor so we don't inflate anything
229   $self->_resultset->cursor->reset;
230   my ($row) = $self->_resultset->cursor->next;
231
232   return $row;
233 }
234
235 =head2 single
236
237 =over 4
238
239 =item Arguments: none
240
241 =item Return Value: $value
242
243 =back
244
245 Much like L<DBIx::Class::ResultSet/single> fetches one and only one column
246 value using the cursor directly. If additional rows are present a warning
247 is issued before discarding the cursor.
248
249 =cut
250
251 sub single {
252   my $self = shift;
253
254   my $attrs = $self->_resultset->_resolved_attrs;
255   my ($row) = $self->_resultset->result_source->storage->select_single(
256     $attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
257   );
258
259   return $row;
260 }
261
262 =head2 min
263
264 =over 4
265
266 =item Arguments: none
267
268 =item Return Value: $lowest_value
269
270 =back
271
272   my $first_year = $year_col->min();
273
274 Wrapper for ->func. Returns the lowest value of the column in the
275 resultset (or C<undef> if there are none).
276
277 =cut
278
279 sub min {
280   return shift->func('MIN');
281 }
282
283 =head2 min_rs
284
285 =over 4
286
287 =item Arguments: none
288
289 =item Return Value: $resultset
290
291 =back
292
293   my $rs = $year_col->min_rs();
294
295 Wrapper for ->func_rs for function MIN().
296
297 =cut
298
299 sub min_rs { return shift->func_rs('MIN') }
300
301 =head2 max
302
303 =over 4
304
305 =item Arguments: none
306
307 =item Return Value: $highest_value
308
309 =back
310
311   my $last_year = $year_col->max();
312
313 Wrapper for ->func. Returns the highest value of the column in the
314 resultset (or C<undef> if there are none).
315
316 =cut
317
318 sub max {
319   return shift->func('MAX');
320 }
321
322 =head2 max_rs
323
324 =over 4
325
326 =item Arguments: none
327
328 =item Return Value: $resultset
329
330 =back
331
332   my $rs = $year_col->max_rs();
333
334 Wrapper for ->func_rs for function MAX().
335
336 =cut
337
338 sub max_rs { return shift->func_rs('MAX') }
339
340 =head2 sum
341
342 =over 4
343
344 =item Arguments: none
345
346 =item Return Value: $sum_of_values
347
348 =back
349
350   my $total = $prices_col->sum();
351
352 Wrapper for ->func. Returns the sum of all the values in the column of
353 the resultset. Use on varchar-like columns at your own risk.
354
355 =cut
356
357 sub sum {
358   return shift->func('SUM');
359 }
360
361 =head2 sum_rs
362
363 =over 4
364
365 =item Arguments: none
366
367 =item Return Value: $resultset
368
369 =back
370
371   my $rs = $year_col->sum_rs();
372
373 Wrapper for ->func_rs for function SUM().
374
375 =cut
376
377 sub sum_rs { return shift->func_rs('SUM') }
378
379 =head2 func
380
381 =over 4
382
383 =item Arguments: $function
384
385 =item Return Value: $function_return_value
386
387 =back
388
389   $rs = $schema->resultset("CD")->search({});
390   $length = $rs->get_column('title')->func('LENGTH');
391
392 Runs a query using the function on the column and returns the
393 value. Produces the following SQL:
394
395   SELECT LENGTH( title ) FROM cd me
396
397 =cut
398
399 sub func {
400   my ($self,$function) = @_;
401   my $cursor = $self->func_rs($function)->cursor;
402
403   if( wantarray ) {
404     return map { $_->[ 0 ] } $cursor->all;
405   }
406
407   return ( $cursor->next )[ 0 ];
408 }
409
410 =head2 func_rs
411
412 =over 4
413
414 =item Arguments: $function
415
416 =item Return Value: $resultset
417
418 =back
419
420 Creates the resultset that C<func()> uses to run its query.
421
422 =cut
423
424 sub func_rs {
425   my ($self,$function) = @_;
426   return $self->{_parent_resultset}->search(
427     undef, {
428       select => {$function => $self->{_select}},
429       as => [$self->{_as}],
430     },
431   );
432 }
433
434 =head2 throw_exception
435
436 See L<DBIx::Class::Schema/throw_exception> for details.
437
438 =cut
439
440 sub throw_exception {
441   my $self=shift;
442
443   if (ref $self && $self->{_parent_resultset}) {
444     $self->{_parent_resultset}->throw_exception(@_);
445   }
446   else {
447     DBIx::Class::Exception->throw(@_);
448   }
449 }
450
451 # _resultset
452 #
453 # Arguments: none
454 #
455 # Return Value: $resultset
456 #
457 #  $year_col->_resultset->next
458 #
459 # Returns the underlying resultset. Creates it from the parent resultset if
460 # necessary.
461 #
462 sub _resultset {
463   my $self = shift;
464
465   return $self->{_resultset} ||= $self->{_parent_resultset}->search(undef,
466     {
467       select => [$self->{_select}],
468       as => [$self->{_as}]
469     }
470   );
471 }
472
473 1;
474
475 =head1 AUTHOR AND CONTRIBUTORS
476
477 See L<AUTHOR|DBIx::Class/AUTHOR> and L<CONTRIBUTORS|DBIx::Class/CONTRIBUTORS> in DBIx::Class
478
479 =head1 LICENSE
480
481 You may distribute this code under the same terms as Perl itself.
482
483 =cut