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