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