File Coverage

blib/lib/DBIx/Class/ResultSetColumn.pm
Criterion Covered Total %
statement 117 128 91.4
branch 22 28 78.5
condition 12 23 52.1
subroutine 27 31 87.1
pod 16 16 100.0
total 194 226 85.8


line stmt bran cond sub pod time code
1             package DBIx::Class::ResultSetColumn;
2              
3 312     312   2555 use strict;
  312         709  
  312         8215  
4 312     312   1631 use warnings;
  312         644  
  312         7896  
5              
6 312     312   1702 use base 'DBIx::Class';
  312         702  
  312         29514  
7 312     312   2065 use DBIx::Class::Carp;
  312         689  
  312         1906  
8 312     312   1916 use DBIx::Class::_Util 'fail_on_internal_call';
  312         780  
  312         14863  
9 312     312   1848 use namespace::clean;
  312         762  
  312         5296  
10              
11             =head1 NAME
12              
13             DBIx::Class::ResultSetColumn - helpful methods for messing
14             with a single column of the resultset
15              
16             =head1 SYNOPSIS
17              
18             $rs = $schema->resultset('CD')->search({ artist => 'Tool' });
19             $rs_column = $rs->get_column('year');
20             $max_year = $rs_column->max; #returns latest year
21              
22             =head1 DESCRIPTION
23              
24             A convenience class used to perform operations on a specific column of
25             a resultset.
26              
27             =cut
28              
29             =head1 METHODS
30              
31             =head2 new
32              
33             my $obj = DBIx::Class::ResultSetColumn->new($rs, $column);
34              
35             Creates a new resultset column object from the resultset and column
36             passed as params. Used internally by L.
37              
38             =cut
39              
40             sub new {
41 717     717 1 2424 my ($class, $rs, $column) = @_;
42 717 50       2390 $class = ref $class if ref $class;
43              
44 717 50       2047 $rs->throw_exception('column must be supplied') unless $column;
45              
46 717         2826 my $orig_attrs = $rs->_resolved_attrs;
47 717         5012 my $alias = $rs->current_source_alias;
48 717         2477 my $rsrc = $rs->result_source;
49              
50             # If $column can be found in the 'as' list of the parent resultset, use the
51             # corresponding element of its 'select' list (to keep any custom column
52             # definition set up with 'select' or '+select' attrs), otherwise use $column
53             # (to create a new column definition on-the-fly).
54 717   50     2910 my $as_list = $orig_attrs->{as} || [];
55 717   50     2584 my $select_list = $orig_attrs->{select} || [];
56 717   50     2654 my ($as_index) = grep { ($as_list->[$_] || "") eq $column } 0..$#$as_list;
  1143         4702  
57 717 100       2749 my $select = defined $as_index ? $select_list->[$as_index] : $column;
58              
59 717         1427 my $colmap;
60 717         19181 for ($rsrc->columns, $column) {
61 4311 100       21564 if ($_ =~ /^ \Q$alias\E \. ([^\.]+) $ /x) {
    50          
62 4         24 $colmap->{$_} = $1;
63             }
64             elsif ($_ !~ /\./) {
65 4307         13736 $colmap->{"$alias.$_"} = $_;
66 4307         11001 $colmap->{$_} = $_;
67             }
68             }
69              
70 717         1790 my $new_parent_rs;
71             # analyze the order_by, and see if it is done over a function/nonexistentcolumn
72             # if this is the case we will need to wrap a subquery since the result of RSC
73             # *must* be a single column select
74 717 100       3106 if (
75             scalar grep
76 21         145 { ! exists $colmap->{$_->[0]} }
77             ( $rsrc->schema->storage->_extract_order_criteria ($orig_attrs->{order_by} ) )
78             ) {
79             # nuke the prefetch before collapsing to sql
80 6         39 my $subq_rs = $rs->search_rs;
81 6         51 $subq_rs->{attrs}{join} = $subq_rs->_merge_joinpref_attr( $subq_rs->{attrs}{join}, delete $subq_rs->{attrs}{prefetch} );
82 6         47 $new_parent_rs = $subq_rs->as_subselect_rs;
83             }
84              
85 717   66     7351 $new_parent_rs ||= $rs->search_rs;
86 717   50     2897 my $new_attrs = $new_parent_rs->{attrs} ||= {};
87              
88             # prefetch causes additional columns to be fetched, but we can not just make a new
89             # rs via the _resolved_attrs trick - we need to retain the separation between
90             # +select/+as and select/as. At the same time we want to preserve any joins that the
91             # prefetch would otherwise generate.
92 717         4930 $new_attrs->{join} = $rs->_merge_joinpref_attr( $new_attrs->{join}, delete $new_attrs->{prefetch} );
93              
94             # {collapse} would mean a has_many join was injected, which in turn means
95             # we need to group *IF WE CAN* (only if the column in question is unique)
96 717 100 100     4696 if (!$orig_attrs->{group_by} && $orig_attrs->{collapse}) {
97              
98 3 50 33     37 if ($colmap->{$select} and $rsrc->_identifying_column_set([$colmap->{$select}])) {
99 3         12 $new_attrs->{group_by} = [ $select ];
100 3         9 delete @{$new_attrs}{qw(distinct _grouped_by_distinct)}; # it is ignored when group_by is present
  3         10  
101             }
102             else {
103 0         0 carp (
104             "Attempting to retrieve non-unique column '$column' on a resultset containing "
105             . 'one-to-many joins will return duplicate results.'
106             );
107             }
108             }
109              
110 717         9177 return bless {
111             _select => $select,
112             _as => $column,
113             _parent_resultset => $new_parent_rs
114             }, $class;
115             }
116              
117             =head2 as_query
118              
119             =over 4
120              
121             =item Arguments: none
122              
123             =item Return Value: \[ $sql, L<@bind_values|DBIx::Class::ResultSet/DBIC BIND VALUES> ]
124              
125             =back
126              
127             Returns the SQL query and bind vars associated with the invocant.
128              
129             This is generally used as the RHS for a subquery.
130              
131             =cut
132              
133 69     69 1 423 sub as_query { return shift->_resultset->as_query(@_) }
134              
135             =head2 next
136              
137             =over 4
138              
139             =item Arguments: none
140              
141             =item Return Value: $value
142              
143             =back
144              
145             Returns the next value of the column in the resultset (or C if
146             there is none).
147              
148             Much like L but just returning the
149             one value.
150              
151             =cut
152              
153             sub next {
154             #my $self = shift;
155              
156             # using cursor so we don't inflate anything
157 603     603 1 14854 ($_[0]->_resultset->cursor->next)[0];
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 if
171             there are none).
172              
173             Much like L but returns values rather
174             than result objects.
175              
176             =cut
177              
178             sub all {
179             #my $self = shift;
180              
181             # using cursor so we don't inflate anything
182 34     34 1 250 map { $_->[0] } $_[0]->_resultset->cursor->all;
  124         623  
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.
199              
200             =cut
201              
202             sub reset {
203             #my $self = shift;
204              
205 0     0 1 0 $_[0]->_resultset->reset;
206 0         0 $_[0];
207             }
208              
209             =head2 first
210              
211             =over 4
212              
213             =item Arguments: none
214              
215             =item Return Value: $value
216              
217             =back
218              
219             Resets the underlying resultset and returns the next value of the column in the
220             resultset (or C if there is none).
221              
222             Much like L but just returning the one value.
223              
224             =cut
225              
226             sub first :DBIC_method_is_indirect_sugar {
227 1     1 1 4 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
228              
229             # using cursor so we don't inflate anything
230 1         7 my $cursor = $_[0]->_resultset->cursor;
231 1         9 $cursor->reset;
232 1         5 ($cursor->next)[0];
233 312     312   268275 }
  312         836  
  312         5783  
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 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 1     1 1 104 my $rs = $_[0]->_resultset;
255              
256 1         13 my $attrs = $rs->_resolved_attrs;
257             ($rs->result_source->schema->storage->select_single(
258 1         17 $attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
259             ))[0];
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 if there are none).
276              
277             =cut
278              
279             sub min :DBIC_method_is_indirect_sugar {
280 2     2 1 8 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
281 2         9 $_[0]->func('MIN');
282 312     312   99303 }
  312         786  
  312         1362  
283              
284             =head2 min_rs
285              
286             =over 4
287              
288             =item Arguments: none
289              
290             =item Return Value: L<$resultset|DBIx::Class::ResultSet>
291              
292             =back
293              
294             my $rs = $year_col->min_rs();
295              
296             Wrapper for ->func_rs for function MIN().
297              
298             =cut
299              
300             sub min_rs :DBIC_method_is_indirect_sugar {
301 0     0 1 0 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
302 0         0 $_[0]->func_rs('MIN')
303 312     312   62708 }
  312         876  
  312         1366  
304              
305             =head2 max
306              
307             =over 4
308              
309             =item Arguments: none
310              
311             =item Return Value: $highest_value
312              
313             =back
314              
315             my $last_year = $year_col->max();
316              
317             Wrapper for ->func. Returns the highest value of the column in the
318             resultset (or C if there are none).
319              
320             =cut
321              
322             sub max :DBIC_method_is_indirect_sugar {
323 9     9 1 39 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
324 9         45 $_[0]->func('MAX');
325 312     312   59670 }
  312         806  
  312         1408  
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             sub max_rs :DBIC_method_is_indirect_sugar {
344 5     5 1 12 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
345 5         26 $_[0]->func_rs('MAX')
346 312     312   59161 }
  312         842  
  312         1366  
347              
348             =head2 sum
349              
350             =over 4
351              
352             =item Arguments: none
353              
354             =item Return Value: $sum_of_values
355              
356             =back
357              
358             my $total = $prices_col->sum();
359              
360             Wrapper for ->func. Returns the sum of all the values in the column of
361             the resultset. Use on varchar-like columns at your own risk.
362              
363             =cut
364              
365             sub sum :DBIC_method_is_indirect_sugar {
366 3     3 1 10 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
367 3         16 $_[0]->func('SUM');
368 312     312   57678 }
  312         718  
  312         3312  
369              
370             =head2 sum_rs
371              
372             =over 4
373              
374             =item Arguments: none
375              
376             =item Return Value: L<$resultset|DBIx::Class::ResultSet>
377              
378             =back
379              
380             my $rs = $year_col->sum_rs();
381              
382             Wrapper for ->func_rs for function SUM().
383              
384             =cut
385              
386             sub sum_rs :DBIC_method_is_indirect_sugar {
387 0     0 1 0 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
388 0         0 $_[0]->func_rs('SUM')
389 312     312   61038 }
  312         779  
  312         1288  
390              
391             =head2 func
392              
393             =over 4
394              
395             =item Arguments: $function
396              
397             =item Return Value: $function_return_value
398              
399             =back
400              
401             $rs = $schema->resultset("CD")->search({});
402             $length = $rs->get_column('title')->func('LENGTH');
403              
404             Runs a query using the function on the column and returns the
405             value. Produces the following SQL:
406              
407             SELECT LENGTH( title ) FROM cd me
408              
409             =cut
410              
411             sub func :DBIC_method_is_indirect_sugar{
412 17     17 1 40 DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
413              
414             #my ($self,$function) = @_;
415 17         86 my $cursor = $_[0]->func_rs($_[1])->cursor;
416              
417             wantarray
418 17 100       194 ? map { $_->[ 0 ] } $cursor->all
  9         45  
419             : ( $cursor->next )[ 0 ]
420             ;
421 312     312   71646 }
  312         817  
  312         1332  
422              
423             =head2 func_rs
424              
425             =over 4
426              
427             =item Arguments: $function
428              
429             =item Return Value: L<$resultset|DBIx::Class::ResultSet>
430              
431             =back
432              
433             Creates the resultset that C uses to run its query.
434              
435             =cut
436              
437             sub func_rs {
438 26     26 1 3282 my ($self,$function) = @_;
439              
440 26         107 my $rs = $self->{_parent_resultset};
441 26         71 my $select = $self->{_select};
442              
443             # wrap a grouped rs
444 26 100       135 if ($rs->_resolved_attrs->{group_by}) {
445 3         13 $select = $self->{_as};
446 3         18 $rs = $rs->as_subselect_rs;
447             }
448              
449             # FIXME - remove at some point in the future (2018-ish)
450             wantarray
451             and
452 26 100       135 carp_unique(
453             'Starting with DBIC@0.082900 func_rs() always returns a ResultSet '
454             . 'instance regardless of calling context. Please force scalar() context to '
455             . 'silence this warning'
456             );
457              
458             $rs->search_rs( undef, {
459 26         424 columns => { $self->{_as} => { $function => $select } }
460             } );
461             }
462              
463             =head2 throw_exception
464              
465             See L for details.
466              
467             =cut
468              
469             sub throw_exception {
470 0     0 1 0 my $self = shift;
471              
472 0 0 0     0 if (ref $self && $self->{_parent_resultset}) {
473 0         0 $self->{_parent_resultset}->throw_exception(@_);
474             }
475             else {
476 0         0 DBIx::Class::Exception->throw(@_);
477             }
478             }
479              
480             # _resultset
481             #
482             # Arguments: none
483             #
484             # Return Value: $resultset
485             #
486             # $year_col->_resultset->next
487             #
488             # Returns the underlying resultset. Creates it from the parent resultset if
489             # necessary.
490             #
491             sub _resultset {
492 713     713   1939 my $self = shift;
493              
494 713   66     3979 return $self->{_resultset} ||= do {
495              
496 699         1796 my $select = $self->{_select};
497              
498 699 100       2451 if ($self->{_parent_resultset}{attrs}{distinct}) {
499 5         25 my $alias = $self->{_parent_resultset}->current_source_alias;
500 5         23 my $rsrc = $self->{_parent_resultset}->result_source;
501 5         97 my %cols = map { $_ => 1, "$alias.$_" => 1 } $rsrc->columns;
  30         90  
502              
503 5 100       30 unless( $cols{$select} ) {
504 2         19 carp_unique(
505             'Use of distinct => 1 while selecting anything other than a column '
506             . 'declared on the primary ResultSource is deprecated (you selected '
507             . "'$self->{_as}') - please supply an explicit group_by instead"
508             );
509              
510             # collapse the selector to a literal so that it survives the distinct parse
511             # if it turns out to be an aggregate - at least the user will get a proper exception
512             # instead of silent drop of the group_by altogether
513 2         322 $select = \[ $rsrc->schema->storage->sql_maker->_recurse_fields($select) ];
514             }
515             }
516              
517             $self->{_parent_resultset}->search_rs(undef, {
518 699         4146 columns => { $self->{_as} => $select }
519             });
520             };
521             }
522              
523             =head1 FURTHER QUESTIONS?
524              
525             Check the list of L.
526              
527             =head1 COPYRIGHT AND LICENSE
528              
529             This module is free software L
530             by the L. You can
531             redistribute it and/or modify it under the same terms as the
532             L.
533              
534             =cut
535              
536             1;
537