File Coverage

blib/lib/DBIx/Mint/ResultSet.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package DBIx::Mint::ResultSet;
2              
3 2     2   2412 use DBIx::Mint;
  0            
  0            
4             use DBIx::Mint::ResultSet::Iterator;
5             use List::MoreUtils qw(uniq);
6             use Clone qw(clone);
7             use Moo;
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             my $self = shift;
38             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             my ($self, $page) = @_;
81             $page = defined $page ? $page : 1;
82             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             my $self = shift;
98             return $self->_join('<=>', @_);
99             }
100              
101             sub left_join {
102             my $self = shift;
103             return $self->_join('=>', @_);
104             }
105              
106             sub _join {
107             my $self = shift;
108             my ($operation, $table, $conditions) = @_;
109             my $table_name;
110             my $table_alias;
111             if (ref $table) {
112             ($table_name, $table_alias) = @$table;
113             }
114             else {
115             $table_name = $table;
116             $table_alias = $table;
117             }
118              
119             my $new_self = $self->_clone;
120             my @join_conditions;
121             while (my ($field1, $field2) = each %$conditions) {
122             if ($field1 !~ /\./) {
123             $field1 = "me.$field1";
124             }
125             if ($field2 !~ /\./) {
126             $field2 = "$table_alias.$field2";
127             }
128             push @join_conditions, "$field1=$field2";
129             }
130             push @{$new_self->joins}, $operation . join(',', @join_conditions), join('|', $table_name, $table_alias);
131             return $new_self;
132             }
133              
134             # Main select method
135             sub select_sql {
136             my $self = shift;
137            
138             # columns
139             my @cols = @{$self->columns} ? uniq(@{$self->columns}) : ('*');
140            
141             # joins
142             my @joins = ($self->table.'|'.'me', @{$self->joins});
143            
144             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             @{$self->list_group_by} ? (-group_by => $self->list_group_by ) : (),
151             @{$self->list_having} ? (-having => $self->list_having ) : (),
152             @{$self->list_order_by} ? (-order_by => $self->list_order_by ) : (),
153             );
154             }
155              
156             sub select_sth {
157             my $self = shift;
158             my ($sql, @bind) = $self->select_sql;
159             my $conn = DBIx::Mint->instance( $self->instance )->connector;
160             return $conn->run(fixup => sub { $_->prepare($sql) }), @bind;
161             }
162              
163             # Fetching data
164              
165             # Returns an array of inflated objects
166             sub all {
167             my $self = shift;
168             my ($sth, @bind) = $self->select_sth;
169             $sth->execute(@bind);
170             my $all = $sth->fetchall_arrayref({});
171             return map { $self->inflate($_) } @$all;
172             }
173              
174             # Returns a single, inflated object
175             sub single {
176             my $self = shift;
177             my ($sth, @bind) = $self->limit(1)->select_sth;
178             $sth->execute(@bind);
179             my $single = $sth->fetchrow_hashref;
180             $sth->finish;
181             return $self->inflate($single);
182             }
183              
184             # Returns a number
185             sub count {
186             my $self = shift;
187             my $clone = $self->_clone;
188             $clone->columns([]);
189             my ($sth, @bind) = $clone->select('COUNT(*)')->select_sth;
190             $sth->execute(@bind);
191             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             my ($self, $row) = @_;
216             return undef unless defined $row;
217             return $row unless $self->has_target_class;
218             $row->{_name} = $self->instance;
219             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