File Coverage

blib/lib/DBIx/Class/ResultSetColumn.pm
Criterion Covered Total %
statement 98 108 90.7
branch 20 26 76.9
condition 11 23 47.8
subroutine 21 25 84.0
pod 16 16 100.0
total 166 198 83.8


line stmt bran cond sub pod time code
1             package DBIx::Class::ResultSetColumn;
2              
3 379     379   2030 use strict;
  379         711  
  379         9641  
4 379     379   1410 use warnings;
  379         709  
  379         8990  
5              
6 379     379   1450 use base 'DBIx::Class';
  379         662  
  379         25123  
7 379     379   1894 use DBIx::Class::Carp;
  379         671  
  379         2196  
8 379     379   1728 use DBIx::Class::_Util 'fail_on_internal_wantarray';
  379         837  
  379         17629  
9 379     379   1755 use namespace::clean;
  379         693  
  379         2270  
10              
11             # not importing first() as it will clash with our own method
12 379     379   79116 use List::Util ();
  379         808  
  379         480196  
13              
14             =head1 NAME
15              
16             DBIx::Class::ResultSetColumn - helpful methods for messing
17             with a single column of the resultset
18              
19             =head1 SYNOPSIS
20              
21             $rs = $schema->resultset('CD')->search({ artist => 'Tool' });
22             $rs_column = $rs->get_column('year');
23             $max_year = $rs_column->max; #returns latest year
24              
25             =head1 DESCRIPTION
26              
27             A convenience class used to perform operations on a specific column of
28             a resultset.
29              
30             =cut
31              
32             =head1 METHODS
33              
34             =head2 new
35              
36             my $obj = DBIx::Class::ResultSetColumn->new($rs, $column);
37              
38             Creates a new resultset column object from the resultset and column
39             passed as params. Used internally by L.
40              
41             =cut
42              
43             sub new {
44 711     711 1 1186 my ($class, $rs, $column) = @_;
45 711 50       1865 $class = ref $class if ref $class;
46              
47 711 50       1666 $rs->throw_exception('column must be supplied') unless $column;
48              
49 711         2100 my $orig_attrs = $rs->_resolved_attrs;
50 711         3378 my $alias = $rs->current_source_alias;
51 711         1881 my $rsrc = $rs->result_source;
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 711   50     2162 my $as_list = $orig_attrs->{as} || [];
58 711   50     1974 my $select_list = $orig_attrs->{select} || [];
59 711   50 956   4265 my $as_index = List::Util::first { ($as_list->[$_] || "") eq $column } 0..$#$as_list;
  956         2745  
60 711 100       2997 my $select = defined $as_index ? $select_list->[$as_index] : $column;
61              
62 711         906 my $colmap;
63 711         2865 for ($rsrc->columns, $column) {
64 4278 100       18927 if ($_ =~ /^ \Q$alias\E \. ([^\.]+) $ /x) {
    50          
65 4         13 $colmap->{$_} = $1;
66             }
67             elsif ($_ !~ /\./) {
68 4274         7645 $colmap->{"$alias.$_"} = $_;
69 4274         7486 $colmap->{$_} = $_;
70             }
71             }
72              
73 711         1117 my $new_parent_rs;
74             # analyze the order_by, and see if it is done over a function/nonexistentcolumn
75             # if this is the case we will need to wrap a subquery since the result of RSC
76             # *must* be a single column select
77 711 100       2335 if (
78             scalar grep
79 21         127 { ! exists $colmap->{$_->[0]} }
80             ( $rsrc->schema->storage->_extract_order_criteria ($orig_attrs->{order_by} ) )
81             ) {
82             # nuke the prefetch before collapsing to sql
83 6         27 my $subq_rs = $rs->search_rs;
84 6         41 $subq_rs->{attrs}{join} = $subq_rs->_merge_joinpref_attr( $subq_rs->{attrs}{join}, delete $subq_rs->{attrs}{prefetch} );
85 6         37 $new_parent_rs = $subq_rs->as_subselect_rs;
86             }
87              
88 711   66     4854 $new_parent_rs ||= $rs->search_rs;
89 711   50     2660 my $new_attrs = $new_parent_rs->{attrs} ||= {};
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 711         3754 $new_attrs->{join} = $rs->_merge_joinpref_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 711 100 66     3812 if (!$orig_attrs->{group_by} && $orig_attrs->{collapse}) {
100              
101 3 50 33     37 if ($colmap->{$select} and $rsrc->_identifying_column_set([$colmap->{$select}])) {
102 3         9 $new_attrs->{group_by} = [ $select ];
103 3         5 delete @{$new_attrs}{qw(distinct _grouped_by_distinct)}; # it is ignored when group_by is present
  3         7  
104             }
105             else {
106 0         0 carp (
107             "Attempting to retrieve non-unique column '$column' on a resultset containing "
108             . 'one-to-many joins will return duplicate results.'
109             );
110             }
111             }
112              
113 711         5795 return bless {
114             _select => $select,
115             _as => $column,
116             _parent_resultset => $new_parent_rs
117             }, $class;
118             }
119              
120             =head2 as_query
121              
122             =over 4
123              
124             =item Arguments: none
125              
126             =item Return Value: \[ $sql, L<@bind_values|DBIx::Class::ResultSet/DBIC BIND VALUES> ]
127              
128             =back
129              
130             Returns the SQL query and bind vars associated with the invocant.
131              
132             This is generally used as the RHS for a subquery.
133              
134             =cut
135              
136 69     69 1 246 sub as_query { return shift->_resultset->as_query(@_) }
137              
138             =head2 next
139              
140             =over 4
141              
142             =item Arguments: none
143              
144             =item Return Value: $value
145              
146             =back
147              
148             Returns the next value of the column in the resultset (or C if
149             there is none).
150              
151             Much like L but just returning the
152             one value.
153              
154             =cut
155              
156             sub next {
157 597     597 1 7414 my $self = shift;
158              
159             # using cursor so we don't inflate anything
160 597         2069 my ($row) = $self->_resultset->cursor->next;
161              
162 593         1896 return $row;
163             }
164              
165             =head2 all
166              
167             =over 4
168              
169             =item Arguments: none
170              
171             =item Return Value: @values
172              
173             =back
174              
175             Returns all values of the column in the resultset (or C if
176             there are none).
177              
178             Much like L but returns values rather
179             than result objects.
180              
181             =cut
182              
183             sub all {
184 34     34 1 69 my $self = shift;
185              
186             # using cursor so we don't inflate anything
187 34         117 return map { $_->[0] } $self->_resultset->cursor->all;
  124         393  
188             }
189              
190             =head2 reset
191              
192             =over 4
193              
194             =item Arguments: none
195              
196             =item Return Value: $self
197              
198             =back
199              
200             Resets the underlying resultset's cursor, so you can iterate through the
201             elements of the column again.
202              
203             Much like L.
204              
205             =cut
206              
207             sub reset {
208 0     0 1 0 my $self = shift;
209 0         0 $self->_resultset->cursor->reset;
210 0         0 return $self;
211             }
212              
213             =head2 first
214              
215             =over 4
216              
217             =item Arguments: none
218              
219             =item Return Value: $value
220              
221             =back
222              
223             Resets the underlying resultset and returns the next value of the column in the
224             resultset (or C if there is none).
225              
226             Much like L but just returning the one value.
227              
228             =cut
229              
230             sub first {
231 1     1 1 1 my $self = shift;
232              
233             # using cursor so we don't inflate anything
234 1         3 $self->_resultset->cursor->reset;
235 1         3 my ($row) = $self->_resultset->cursor->next;
236              
237 1         4 return $row;
238             }
239              
240             =head2 single
241              
242             =over 4
243              
244             =item Arguments: none
245              
246             =item Return Value: $value
247              
248             =back
249              
250             Much like L fetches one and only one column
251             value using the cursor directly. If additional rows are present a warning
252             is issued before discarding the cursor.
253              
254             =cut
255              
256             sub single {
257 1     1 1 44 my $self = shift;
258              
259 1         2 my $attrs = $self->_resultset->_resolved_attrs;
260             my ($row) = $self->_resultset->result_source->storage->select_single(
261 1         4 $attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
262             );
263              
264 1         4 return $row;
265             }
266              
267             =head2 min
268              
269             =over 4
270              
271             =item Arguments: none
272              
273             =item Return Value: $lowest_value
274              
275             =back
276              
277             my $first_year = $year_col->min();
278              
279             Wrapper for ->func. Returns the lowest value of the column in the
280             resultset (or C if there are none).
281              
282             =cut
283              
284             sub min {
285 2     2 1 798 return shift->func('MIN');
286             }
287              
288             =head2 min_rs
289              
290             =over 4
291              
292             =item Arguments: none
293              
294             =item Return Value: L<$resultset|DBIx::Class::ResultSet>
295              
296             =back
297              
298             my $rs = $year_col->min_rs();
299              
300             Wrapper for ->func_rs for function MIN().
301              
302             =cut
303              
304 0     0 1 0 sub min_rs { return shift->func_rs('MIN') }
305              
306             =head2 max
307              
308             =over 4
309              
310             =item Arguments: none
311              
312             =item Return Value: $highest_value
313              
314             =back
315              
316             my $last_year = $year_col->max();
317              
318             Wrapper for ->func. Returns the highest value of the column in the
319             resultset (or C if there are none).
320              
321             =cut
322              
323             sub max {
324 9     9 1 343 return shift->func('MAX');
325             }
326              
327             =head2 max_rs
328              
329             =over 4
330              
331             =item Arguments: none
332              
333             =item Return Value: L<$resultset|DBIx::Class::ResultSet>
334              
335             =back
336              
337             my $rs = $year_col->max_rs();
338              
339             Wrapper for ->func_rs for function MAX().
340              
341             =cut
342              
343 5     5 1 19 sub max_rs { return shift->func_rs('MAX') }
344              
345             =head2 sum
346              
347             =over 4
348              
349             =item Arguments: none
350              
351             =item Return Value: $sum_of_values
352              
353             =back
354              
355             my $total = $prices_col->sum();
356              
357             Wrapper for ->func. Returns the sum of all the values in the column of
358             the resultset. Use on varchar-like columns at your own risk.
359              
360             =cut
361              
362             sub sum {
363 3     3 1 410 return shift->func('SUM');
364             }
365              
366             =head2 sum_rs
367              
368             =over 4
369              
370             =item Arguments: none
371              
372             =item Return Value: L<$resultset|DBIx::Class::ResultSet>
373              
374             =back
375              
376             my $rs = $year_col->sum_rs();
377              
378             Wrapper for ->func_rs for function SUM().
379              
380             =cut
381              
382 0     0 1 0 sub sum_rs { return shift->func_rs('SUM') }
383              
384             =head2 func
385              
386             =over 4
387              
388             =item Arguments: $function
389              
390             =item Return Value: $function_return_value
391              
392             =back
393              
394             $rs = $schema->resultset("CD")->search({});
395             $length = $rs->get_column('title')->func('LENGTH');
396              
397             Runs a query using the function on the column and returns the
398             value. Produces the following SQL:
399              
400             SELECT LENGTH( title ) FROM cd me
401              
402             =cut
403              
404             sub func {
405 17     17 1 1259 my ($self,$function) = @_;
406 17         44 my $cursor = $self->func_rs($function)->cursor;
407              
408 17 100       71 if( wantarray ) {
409 3         2 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_WANTARRAY and my $sog = fail_on_internal_wantarray;
410 3         14 return map { $_->[ 0 ] } $cursor->all;
  9         27  
411             }
412              
413 14         46 return ( $cursor->next )[ 0 ];
414             }
415              
416             =head2 func_rs
417              
418             =over 4
419              
420             =item Arguments: $function
421              
422             =item Return Value: L<$resultset|DBIx::Class::ResultSet>
423              
424             =back
425              
426             Creates the resultset that C uses to run its query.
427              
428             =cut
429              
430             sub func_rs {
431 24     24 1 41 my ($self,$function) = @_;
432              
433 24         61 my $rs = $self->{_parent_resultset};
434 24         43 my $select = $self->{_select};
435              
436             # wrap a grouped rs
437 24 100       79 if ($rs->_resolved_attrs->{group_by}) {
438 3         4 $select = $self->{_as};
439 3         9 $rs = $rs->as_subselect_rs;
440             }
441              
442             $rs->search( undef, {
443 24         174 columns => { $self->{_as} => { $function => $select } }
444             } );
445             }
446              
447             =head2 throw_exception
448              
449             See L for details.
450              
451             =cut
452              
453             sub throw_exception {
454 0     0 1 0 my $self = shift;
455              
456 0 0 0     0 if (ref $self && $self->{_parent_resultset}) {
457 0         0 $self->{_parent_resultset}->throw_exception(@_);
458             }
459             else {
460 0         0 DBIx::Class::Exception->throw(@_);
461             }
462             }
463              
464             # _resultset
465             #
466             # Arguments: none
467             #
468             # Return Value: $resultset
469             #
470             # $year_col->_resultset->next
471             #
472             # Returns the underlying resultset. Creates it from the parent resultset if
473             # necessary.
474             #
475             sub _resultset {
476 709     709   1070 my $self = shift;
477              
478 709   66     3362 return $self->{_resultset} ||= do {
479              
480 693         1157 my $select = $self->{_select};
481              
482 693 100       2086 if ($self->{_parent_resultset}{attrs}{distinct}) {
483 5         15 my $alias = $self->{_parent_resultset}->current_source_alias;
484 5         14 my $rsrc = $self->{_parent_resultset}->result_source;
485 5         16 my %cols = map { $_ => 1, "$alias.$_" => 1 } $rsrc->columns;
  30         76  
486              
487 5 100       29 unless( $cols{$select} ) {
488 2         16 carp_unique(
489             'Use of distinct => 1 while selecting anything other than a column '
490             . 'declared on the primary ResultSource is deprecated (you selected '
491             . "'$self->{_as}') - please supply an explicit group_by instead"
492             );
493              
494             # collapse the selector to a literal so that it survives the distinct parse
495             # if it turns out to be an aggregate - at least the user will get a proper exception
496             # instead of silent drop of the group_by altogether
497 2         125 $select = \[ $rsrc->storage->sql_maker->_recurse_fields($select) ];
498             }
499             }
500              
501             $self->{_parent_resultset}->search(undef, {
502 693         3948 columns => { $self->{_as} => $select }
503             });
504             };
505             }
506              
507             =head1 FURTHER QUESTIONS?
508              
509             Check the list of L.
510              
511             =head1 COPYRIGHT AND LICENSE
512              
513             This module is free software L
514             by the L. You can
515             redistribute it and/or modify it under the same terms as the
516             L.
517              
518             =cut
519              
520             1;
521