File Coverage

blib/lib/DBIx/Class/ResultSetColumn.pm
Criterion Covered Total %
statement 98 108 90.7
branch 20 26 76.9
condition 12 23 52.1
subroutine 21 25 84.0
pod 16 16 100.0
total 167 198 84.3


line stmt bran cond sub pod time code
1             package DBIx::Class::ResultSetColumn;
2              
3 379     379   4007 use strict;
  379         1327  
  379         11079  
4 379     379   2088 use warnings;
  379         1100  
  379         10300  
5              
6 379     379   2162 use base 'DBIx::Class';
  379         1198  
  379         38472  
7 379     379   2911 use DBIx::Class::Carp;
  379         1253  
  379         2609  
8 379     379   2927 use DBIx::Class::_Util 'fail_on_internal_wantarray';
  379         1346  
  379         19689  
9 379     379   2683 use namespace::clean;
  379         1241  
  379         2752  
10              
11             # not importing first() as it will clash with our own method
12 379     379   96941 use List::Util ();
  379         1343  
  379         574722  
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<DBIx::Class::ResultSet/get_column>.
40              
41             =cut
42              
43             sub new {
44 714     714 1 2004 my ($class, $rs, $column) = @_;
45 714 50       2098 $class = ref $class if ref $class;
46              
47 714 50       1994 $rs->throw_exception('column must be supplied') unless $column;
48              
49 714         2320 my $orig_attrs = $rs->_resolved_attrs;
50 714         3939 my $alias = $rs->current_source_alias;
51 714         2284 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 714   50     2438 my $as_list = $orig_attrs->{as} || [];
58 714   50     2173 my $select_list = $orig_attrs->{select} || [];
59 714   50 959   4718 my $as_index = List::Util::first { ($as_list->[$_] || "") eq $column } 0..$#$as_list;
  959         3233  
60 714 100       3740 my $select = defined $as_index ? $select_list->[$as_index] : $column;
61              
62 714         1335 my $colmap;
63 714         3033 for ($rsrc->columns, $column) {
64 4299 100       20234 if ($_ =~ /^ \Q$alias\E \. ([^\.]+) $ /x) {
    50          
65 4         17 $colmap->{$_} = $1;
66             }
67             elsif ($_ !~ /\./) {
68 4295         11696 $colmap->{"$alias.$_"} = $_;
69 4295         10227 $colmap->{$_} = $_;
70             }
71             }
72              
73 714         1731 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 714 100       2577 if (
78             scalar grep
79 21         124 { ! 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         26 my $subq_rs = $rs->search_rs;
84 6         34 $subq_rs->{attrs}{join} = $subq_rs->_merge_joinpref_attr( $subq_rs->{attrs}{join}, delete $subq_rs->{attrs}{prefetch} );
85 6         118 $new_parent_rs = $subq_rs->as_subselect_rs;
86             }
87              
88 714   66     5110 $new_parent_rs ||= $rs->search_rs;
89 714   50     2767 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 714         4076 $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 714 100 100     4360 if (!$orig_attrs->{group_by} && $orig_attrs->{collapse}) {
100              
101 3 50 33     44 if ($colmap->{$select} and $rsrc->_identifying_column_set([$colmap->{$select}])) {
102 3         12 $new_attrs->{group_by} = [ $select ];
103 3         10 delete @{$new_attrs}{qw(distinct _grouped_by_distinct)}; # it is ignored when group_by is present
  3         11  
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 714         6309 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 287 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<undef> if
149             there is none).
150              
151             Much like L<DBIx::Class::ResultSet/next> but just returning the
152             one value.
153              
154             =cut
155              
156             sub next {
157 600     600 1 9289 my $self = shift;
158              
159             # using cursor so we don't inflate anything
160 600         2415 my ($row) = $self->_resultset->cursor->next;
161              
162 596         2213 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<undef> if
176             there are none).
177              
178             Much like L<DBIx::Class::ResultSet/all> but returns values rather
179             than result objects.
180              
181             =cut
182              
183             sub all {
184 34     34 1 100 my $self = shift;
185              
186             # using cursor so we don't inflate anything
187 34         137 return map { $_->[0] } $self->_resultset->cursor->all;
  124         542  
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<DBIx::Class::ResultSet/reset>.
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<undef> if there is none).
225              
226             Much like L<DBIx::Class::ResultSet/first> but just returning the one value.
227              
228             =cut
229              
230             sub first {
231 1     1 1 3 my $self = shift;
232              
233             # using cursor so we don't inflate anything
234 1         4 $self->_resultset->cursor->reset;
235 1         3 my ($row) = $self->_resultset->cursor->next;
236              
237 1         7 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<DBIx::Class::ResultSet/single> 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 62 my $self = shift;
258              
259 1         3 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         5 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<undef> if there are none).
281              
282             =cut
283              
284             sub min {
285 2     2 1 1012 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<undef> if there are none).
320              
321             =cut
322              
323             sub max {
324 9     9 1 355 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 23 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 537 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 1180 my ($self,$function) = @_;
406 17         49 my $cursor = $self->func_rs($function)->cursor;
407              
408 17 100       93 if( wantarray ) {
409 3         4 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_WANTARRAY and my $sog = fail_on_internal_wantarray;
410 3         12 return map { $_->[ 0 ] } $cursor->all;
  9         30  
411             }
412              
413 14         53 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<func()> uses to run its query.
427              
428             =cut
429              
430             sub func_rs {
431 24     24 1 62 my ($self,$function) = @_;
432              
433 24         70 my $rs = $self->{_parent_resultset};
434 24         54 my $select = $self->{_select};
435              
436             # wrap a grouped rs
437 24 100       82 if ($rs->_resolved_attrs->{group_by}) {
438 3         7 $select = $self->{_as};
439 3         8 $rs = $rs->as_subselect_rs;
440             }
441              
442             $rs->search( undef, {
443 24         182 columns => { $self->{_as} => { $function => $select } }
444             } );
445             }
446              
447             =head2 throw_exception
448              
449             See L<DBIx::Class::Schema/throw_exception> 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 712     712   1386 my $self = shift;
477              
478 712   66     3617 return $self->{_resultset} ||= do {
479              
480 696         1608 my $select = $self->{_select};
481              
482 696 100       2312 if ($self->{_parent_resultset}{attrs}{distinct}) {
483 5         20 my $alias = $self->{_parent_resultset}->current_source_alias;
484 5         17 my $rsrc = $self->{_parent_resultset}->result_source;
485 5         18 my %cols = map { $_ => 1, "$alias.$_" => 1 } $rsrc->columns;
  30         82  
486              
487 5 100       31 unless( $cols{$select} ) {
488 2         18 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         186 $select = \[ $rsrc->storage->sql_maker->_recurse_fields($select) ];
498             }
499             }
500              
501             $self->{_parent_resultset}->search(undef, {
502 696         3961 columns => { $self->{_as} => $select }
503             });
504             };
505             }
506              
507             =head1 FURTHER QUESTIONS?
508              
509             Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
510              
511             =head1 COPYRIGHT AND LICENSE
512              
513             This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
514             by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
515             redistribute it and/or modify it under the same terms as the
516             L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
517              
518             =cut
519              
520             1;
521