File Coverage

blib/lib/DBIx/Mint/ResultSet.pm
Criterion Covered Total %
statement 80 80 100.0
branch 24 24 100.0
condition n/a
subroutine 17 17 100.0
pod 5 9 55.5
total 126 130 96.9


line stmt bran cond sub pod time code
1             package DBIx::Mint::ResultSet;
2              
3 15     15   347 use DBIx::Mint;
  15         17  
  15         362  
4 15     15   4544 use DBIx::Mint::ResultSet::Iterator;
  15         28  
  15         376  
5 15     15   6678 use List::MoreUtils qw(uniq);
  15         117834  
  15         98  
6 15     15   11975 use Clone qw(clone);
  15         26468  
  15         844  
7 15     15   69 use Moo;
  15         28  
  15         88  
8              
9             has instance => ( is => 'ro', default => sub { '_DEFAULT' });
10              
11             has table => ( is => 'rw', required => 1 );
12             has target_class => ( is => 'rw', predicate => 1 );
13             has columns => ( is => 'rw', default => sub {[]});
14             has where => ( is => 'rw', default => sub {[]});
15             has joins => ( is => 'rw', default => sub {[]});
16              
17             has rows_per_page => ( is => 'rw', default => sub {10} );
18             has set_limit => ( is => 'rw', predicate => 1 );
19             has set_offset => ( is => 'rw', predicate => 1 );
20              
21             has list_group_by => ( is => 'rw', default => sub {[]});
22             has list_having => ( is => 'rw', default => sub {[]});
23             has list_order_by => ( is => 'rw', default => sub {[]});
24              
25             has iterator => ( is => 'rw', predicate => 1, handles => ['next'] );
26              
27             around 'select', 'search', 'group_by', 'having', 'order_by', 'set_target_class',
28             'limit', 'offset', 'set_rows_per_page', 'as_iterator' => sub {
29             my $orig = shift;
30             my $self = shift;
31             my $clone = $self->_clone;
32             $clone->$orig(@_);
33             return $clone;
34             };
35              
36             sub _clone {
37 137     137   117 my $self = shift;
38 137         2593 return clone $self;
39             }
40              
41              
42             # Query building pieces
43              
44             sub select {
45             my $self = shift;
46             push @{ $self->columns }, @_;
47             }
48              
49             sub search {
50             my $self = shift;
51             push @{ $self->where }, @_;
52             }
53              
54             sub group_by {
55             my $self = shift;
56             push @{ $self->list_group_by }, @_;
57             }
58              
59             sub having {
60             my $self = shift;
61             push @{ $self->list_having }, @_;
62             }
63              
64             sub order_by {
65             my $self = shift;
66             push @{ $self->list_order_by }, @_;
67             }
68              
69             sub limit {
70             my ($self, $value) = @_;
71             $self->set_limit($value);
72             }
73              
74             sub offset {
75             my ($self, $value) = @_;
76             $self->set_offset($value);
77             }
78              
79             sub page {
80 3     3 1 3059 my ($self, $page) = @_;
81 3 100       6 $page = defined $page ? $page : 1;
82 3         67 return $self->limit( $self->rows_per_page )
83             ->offset($self->rows_per_page * ( $page - 1 ));
84             }
85              
86             sub set_rows_per_page {
87             my ($self, $value) = @_;
88             $self->rows_per_page($value);
89             }
90              
91             # Joins
92             # Input:
93             # table (array ref): [name, alias] or name
94             # conditions (array of hashes): [{ left_field => 'right_field' }
95              
96             sub inner_join {
97 27     27 0 3141 my $self = shift;
98 27         63 return $self->_join('<=>', @_);
99             }
100              
101             sub left_join {
102 1     1 0 1 my $self = shift;
103 1         2 return $self->_join('=>', @_);
104             }
105              
106             sub _join {
107 28     28   26 my $self = shift;
108 28         36 my ($operation, $table, $conditions) = @_;
109 28         22 my $table_name;
110             my $table_alias;
111 28 100       49 if (ref $table) {
112 4         7 ($table_name, $table_alias) = @$table;
113             }
114             else {
115 24         22 $table_name = $table;
116 24         19 $table_alias = $table;
117             }
118              
119 28         51 my $new_self = $self->_clone;
120 28         33 my @join_conditions;
121 28         84 while (my ($field1, $field2) = each %$conditions) {
122 32 100       85 if ($field1 !~ /\./) {
123 3         6 $field1 = "me.$field1";
124             }
125 32 100       66 if ($field2 !~ /\./) {
126 4         6 $field2 = "$table_alias.$field2";
127             }
128 32         94 push @join_conditions, "$field1=$field2";
129             }
130 28         25 push @{$new_self->joins}, $operation . join(',', @join_conditions), join('|', $table_name, $table_alias);
  28         108  
131 28         74 return $new_self;
132             }
133              
134             # Main select method
135             sub select_sql {
136 50     50 1 7158 my $self = shift;
137            
138             # columns
139 50 100       63 my @cols = @{$self->columns} ? uniq(@{$self->columns}) : ('*');
  50         188  
  18         115  
140            
141             # joins
142 50         137 my @joins = ($self->table.'|'.'me', @{$self->joins});
  50         116  
143            
144 50         136 return DBIx::Mint->instance( $self->instance )->abstract->select(
145             -columns => \@cols,
146             -from => [ -join => @joins ],
147             -where => [ -and => $self->where ],
148             $self->has_set_limit ? (-limit => $self->set_limit ) : (),
149             $self->has_set_offset ? (-offset => $self->set_offset ) : (),
150 50         113 @{$self->list_group_by} ? (-group_by => $self->list_group_by ) : (),
151 50         253 @{$self->list_having} ? (-having => $self->list_having ) : (),
152 50 100       212 @{$self->list_order_by} ? (-order_by => $self->list_order_by ) : (),
    100          
    100          
    100          
    100          
153             );
154             }
155              
156             sub select_sth {
157 27     27 0 63 my $self = shift;
158 27         66 my ($sql, @bind) = $self->select_sql;
159 27         20287 my $conn = DBIx::Mint->instance( $self->instance )->connector;
160 27     27   145 return $conn->run(fixup => sub { $_->prepare($sql) }), @bind;
  27         942  
161             }
162              
163             # Fetching data
164              
165             # Returns an array of inflated objects
166             sub all {
167 11     11 1 419 my $self = shift;
168 11         30 my ($sth, @bind) = $self->select_sth;
169 11         1724 $sth->execute(@bind);
170 11         106 my $all = $sth->fetchall_arrayref({});
171 11         723 return map { $self->inflate($_) } @$all;
  36         55  
172             }
173              
174             # Returns a single, inflated object
175             sub single {
176 11     11 1 23 my $self = shift;
177 11         248 my ($sth, @bind) = $self->limit(1)->select_sth;
178 11         1861 $sth->execute(@bind);
179 11         319 my $single = $sth->fetchrow_hashref;
180 11         60 $sth->finish;
181 11         31 return $self->inflate($single);
182             }
183              
184             # Returns a number
185             sub count {
186 1     1 1 715 my $self = shift;
187 1         52 my $clone = $self->_clone;
188 1         4 $clone->columns([]);
189 1         28 my ($sth, @bind) = $clone->select('COUNT(*)')->select_sth;
190 1         89 $sth->execute(@bind);
191 1         21 return $sth->fetchall_arrayref->[0][0];
192             }
193              
194             # Creates an iterator and saves it into the ResultSet object
195             sub as_iterator {
196             my $self = shift;
197             my ($sth, @bind) = $self->select_sth;
198             $sth->execute(@bind);
199            
200             my $iterator = DBIx::Mint::ResultSet::Iterator->new(
201             closure => sub { return $self->inflate($sth->fetchrow_hashref); },
202             );
203            
204             $self->iterator( $iterator );
205             }
206              
207             # Set the class we bless rows into
208             sub set_target_class {
209             my ($self, $target) = @_;
210             $self->target_class($target);
211             }
212              
213             # Simply blesses the fetched row into the target class
214             sub inflate {
215 69     69 0 70 my ($self, $row) = @_;
216 69 100       131 return undef unless defined $row;
217 65 100       224 return $row unless $self->has_target_class;
218 48         83 $row->{_name} = $self->instance;
219 48         342 return bless $row, $self->target_class;
220             }
221              
222             1;
223              
224             =pod
225              
226             =head1 NAME
227              
228             DBIx::Mint::ResultSet - DBIx::Mint class to build database queries
229              
230             =head1 SYNOPSIS
231              
232             # Create your ResultSet object:
233             my $rs = DBIx::Mint::ResultSet->new( table => 'teams' );
234            
235             # Now, build your query:
236             $rs = $rs->select( 'name', 'slogan', 'logo' )->search({ group => 'A'});
237            
238             # Join tables
239             $rs = DBIx::Mint::ResultSet
240             ->new( table => 'teams' )
241             ->inner_join('players', { id => 'teams'});
242            
243             # Fetch data
244             $rs->set_target_class( 'Bloodbowl::Team' );
245             my @teams = $rs->all;
246             my $team = $rs->single;
247            
248             $rs->as_iterator;
249             while (my $team = $rs->next) {
250             say $team->slogan;
251             }
252            
253             =head1 DESCRIPTION
254              
255             Objects of this class allow you to fetch information from the database. ResultSet objects do not know about the database schema, which means that you can use them without one and that you must use table names directly (but see L for getting objects from a specific class).
256              
257             Query creation and join methods return a clone of the original ResultSet object. This makes them chaineable.
258              
259             Records can be returned as hash references or they can be inflated to the target class you set. You can get a single result, a list of all results, or an iterator.
260              
261             =head1 METHODS
262              
263             =head2 CONSTRUCTOR
264              
265             =over
266              
267             =item new
268              
269             It expects two arguments:
270              
271             =over
272              
273             =item table
274              
275             Used as the table to start building queries. You will join to this table or fetch data from this table. Required.
276              
277             =item instance
278              
279             Name of the L instance to use.
280              
281             =back
282              
283             =back
284              
285             =head2 QUERY CREATION METHODS
286              
287             =over
288              
289             =item select
290              
291             Takes a list of field names to fetch from the given table or join. This method can be called several times to add different fields.
292              
293             =item search
294              
295             Builds the 'where' part of the query. It takes a data structure defined per the syntax of L.
296              
297             =item order_by, limit, offset, group_by, having
298              
299             These methods feed the L select method with their respective clause.
300              
301             =item page, set_rows_per_page
302              
303             These methods help in pagination of query results. They let you set the number of records per page (C) and to fetch a given C. The default for C is 10 records.
304              
305             They work by setting LIMIT and OFFSET in the SQL query.
306              
307             =back
308              
309             =head2 TABLE JOINS
310              
311             L offers inner and left joins between tables. The syntax is quite simple:
312              
313             $rs->new( table => 'coaches' )->inner_join( 'teams', { id => 'coach' });
314              
315             The above call would produce a join between the tables 'coaches' and 'teams' using the fields 'id' from coaches and 'coach' from teams.
316              
317             $rs->new( table => 'coaches' )
318             ->inner_join( ['teams', 't'], { 'me.id' => 't.coach' })
319             ->left_join( ['players', 'p'], { 't.id' => 'p.team' });
320              
321             You can alias the table names. 'me' always refers to the starting table (coaches in the example above).
322              
323             Note that the first example does not include table aliases. In this case, the keys of the hash reference are fields of the starting table (coaches) and its values refer to the table that will be joined. If you don't use aliases, joins always refer to the initial table.
324              
325             =head2 FETCHING DATA
326              
327             =over
328              
329             =item select_sql
330              
331             This method will return a SQL select statement and a list of values to bind, most helpful when debugging:
332              
333             my ($sql, @bind) = $rs->select_sql;
334            
335             =item set_taget_class
336              
337             While not precisely a fetching method, it does define the class to bless fetched records. It is called like this:
338              
339             $rs = $rs->set_target_class('Bloodbowl::Coach');
340              
341             =item single
342              
343             This method will return a single record from your query. It sets LIMIT to 1 and calls finish on the DBI statement holder. It returns a blessed object if you have set a target class earlier.
344              
345             =item all
346              
347             Returns a list with all the records that result from your query. The records will be inflated to the target class if it was set earlier.
348              
349             =item as_iterator
350              
351             This will add an iterator to the ResultSet object, over which you must call 'next' to fetch a record:
352              
353             $rs->as_iterator;
354             while (my $record = $rs->next ) {
355             say $record->name;
356             }
357              
358             This is the most efficient way to retrieve records.
359              
360             =item count
361              
362             This method will return the number of records matching your query. Internally, it builds a new query with the same search criteria as your original one, and asks for the count of matching records. Use it before adding pagination or other result-limiting constaints:
363              
364             $rs = $rs->select( 'name', 'slogan', 'logo' )->search({ group => 'A'});
365             my $count = $rs->count;
366             my @records = $rs->set_rows_per_page(10)->page(5)->all;
367              
368             =back
369              
370             =head1 SEE ALSO
371              
372             This module is part of L.
373            
374             =head1 ACKNOWLEDGEMENTS
375              
376             This module is heavily based on L, by Alessandro Ranellucci.
377              
378             =head1 AUTHOR
379              
380             Julio Fraire,
381              
382             =head1 LICENCE AND COPYRIGHT
383              
384             Copyright (c) 2013, Julio Fraire. All rights reserved.
385              
386             =head1 LICENSE
387              
388             This module is free software; you can redistribute it and/or
389             modify it under the same terms as Perl itself. See L.
390              
391             This program is distributed in the hope that it will be useful,
392             but WITHOUT ANY WARRANTY; without even the implied warranty of
393             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
394              
395             =cut
396            
397