File Coverage

blib/lib/DBR/ResultSet.pm
Criterion Covered Total %
statement 138 190 72.6
branch 23 64 35.9
condition 11 11 100.0
subroutine 23 29 79.3
pod 0 13 0.0
total 195 307 63.5


line stmt bran cond sub pod time code
1             package DBR::ResultSet;
2              
3 18     18   104 use strict;
  18         32  
  18         838  
4 18     18   107 use base 'DBR::Common';
  18         41  
  18         2059  
5              
6 18     18   114 use DBR::Misc::Dummy;
  18         41  
  18         437  
7 18     18   135 use Carp;
  18         3157  
  18         6196  
8 18     18   119 use Scalar::Util 'weaken';
  18         1623  
  18         6559  
9             use constant ({
10 18         101061 f_next => 0,
11             f_state => 1,
12             f_rowcache => 2,
13             f_query => 3,
14             f_count => 4,
15             f_splitval => 5,
16              
17             stCLEAN => 1,
18             stACTIVE => 2,
19             stMEM => 3,
20              
21             FIRST => \&_first,
22             DUMMY => bless([],'DBR::Misc::Dummy'),
23 18     18   29713 });
  18         1475  
24              
25              
26             sub new {
27 57     57 0 263 my ( $package, $query, $splitval ) = @_;
28              
29             #the sequence of this MUST line up with the fields above
30 57         418 return bless( [
31             FIRST, # next
32             stCLEAN, # state
33             [], # rowcache - placeholder
34             $query, # query
35             undef, # count
36             $splitval,# splitval
37             ], $package );
38             }
39              
40              
41 116     116 0 56482 sub next { $_[0][ f_next ]->( $_[0] ) }
42              
43             sub dump{
44 0     0 0 0 my $self = shift;
45 0         0 my @fields = map { split(/\s+/,$_) } @_;
  0         0  
46              
47 0 0       0 map { croak "invalid field '$_'" unless /^[A-Za-z0-9_\.]+$/ } @fields;
  0         0  
48              
49              
50 0         0 my $code = 'while(my $rec = $self->next){ push @out, {' . "\n";
51              
52 0         0 foreach my $field ( @fields){
53 0         0 my $f = $field;
54 0         0 $f =~ s/\./->/g;
55 0         0 $code .= "'$field' => \$rec->$f,\n";
56             }
57              
58 0         0 $code .= "}}";
59 0         0 my @out;
60 0         0 eval $code;
61              
62 0 0       0 die "eval returned '$@'" if $@;
63              
64 0 0       0 wantarray ? @out : \@out;
65             }
66              
67             sub TO_JSON {
68 0     0 0 0 my $self = shift;
69              
70 0         0 return $self->dump(
71 0         0 map { $_->name } @{ $self->[f_query]->primary_table->fields }
  0         0  
72             );
73              
74             } #Dump it all
75              
76             sub reset{
77 0     0 0 0 my $self = shift;
78              
79 0 0       0 if ($self->[f_state] == stMEM){
80 0         0 return $self->_mem_iterator; #rowcache is already full, reset the mem iterator
81             }
82              
83 0 0       0 if( $self->[f_state] == stACTIVE ){
84 0         0 $self->[f_query]->reset; # calls finish
85 0         0 $self->[f_rowcache] = []; #not sure if this is necessary or not
86 0         0 $self->[f_state] = stCLEAN;
87 0         0 $self->[f_next] = FIRST;
88             }
89              
90 0         0 return 1;
91             }
92              
93             sub _first{
94 25     25   243 my $self = shift;
95              
96 25         121 $self->_execute();
97 25         142 return $self->next;
98             }
99              
100             sub _execute{
101 34     34   71 my $self = shift;
102              
103 34 50       157 $self->[f_state] == stCLEAN or croak "Cannot call _execute unless in a clean state";
104              
105 34 100       165 if( defined( $self->[f_splitval] ) ){
106              
107 10         65 my $rows = $self->[f_rowcache] = $self->[f_query]->fetch_segment( $self->[f_splitval] ); # Query handles the sth
108 10         50 $self->_mem_iterator;
109              
110             }else{
111              
112 24         125 $self->_db_iterator;
113              
114             }
115              
116 34         88 return 1;
117             }
118              
119             sub _db_iterator{
120 24     24   58 my $self = shift;
121              
122              
123 24         159 my $record = $self->[f_query]->get_record_obj;
124 24         137 my $class = $record->class;
125              
126 24         170 my $sth = $self->[f_query]->run;
127              
128 24 50       3416 defined( my $rv = $sth->execute ) or confess 'failed to execute statement (' . $sth->errstr. ')';
129              
130 24         106 $self->[f_state] = stACTIVE;
131              
132 24 50       147 if( $self->[f_query]->instance->getconn->can_trust_execute_rowcount ){ # HERE - yuck... assumes this is same connection as the sth
133 0         0 $self->[f_count] = $rv + 0;
134 0         0 $self->[f_query]->_logDebug3('ROWS: ' . ($rv + 0));
135             }
136              
137            
138              
139             # IMPORTANT NOTE: circular reference hazard
140 24         137 weaken ($self); # Weaken the refcount
141              
142             my $endsub = sub {
143 16 50   16   81 defined($self) or return DUMMY; # technically this could be out of scope because it's a weak ref
144              
145 16   100     253 $self->[f_count] ||= $sth->rows || 0;
      100        
146 16         45 $self->[f_next] = FIRST;
147 16         38 $self->[f_state] = stCLEAN; # If we get here, then we hit the end, and no ->finish is required
148              
149 16         266 return DUMMY; # evaluates to false
150 24         188 };
151              
152 24         1520 my $buddy;
153 24         66 my $rows = [];
154 24         50 my $commonref;
155             my $getchunk = sub {
156 35   100 35   1307 $rows = $sth->fetchall_arrayref(undef,1000) || return undef; # if cache is empty, fetch more
157            
158 21         76 $commonref = [ @$rows ];
159 21         55 map {weaken $_} @$commonref;
  45         151  
160 21         61 $buddy = [ $commonref, $record ]; # buddy ref must contain the record object just to keep it in scope.
161            
162 21         309 return shift @$rows;
163 24         210 };
164             # use a closure to reduce hash lookups
165             # It's very important that this closure is fast.
166             # This one routine has more of an effect on speed than anything else in the rest of the code
167              
168             $self->[f_next] = sub {
169 61   100 61   598 bless(
170             (
171             [
172             (
173             shift(@$rows) || $getchunk->() || return $endsub->()
174             ),
175             $buddy
176             ]
177             ),
178             $class
179             );
180 24         329 };
181              
182 24         97 return 1;
183              
184             }
185              
186             sub _mem_iterator{
187 13     13   27 my $self = shift;
188              
189 13         68 my $record = $self->[f_query]->get_record_obj;
190 13         64 my $class = $record->class;
191              
192 13         62 my $buddy = [ $self->[f_rowcache], $record ]; # buddy ref must contain the record object just to keep it in scope.
193              
194 13         35 my $rows = $self->[f_rowcache];
195 13         28 my $ct = 0;
196              
197             # use a closure to reduce hash lookups
198             # It's very important that this closure is fast.
199             # This one routine has more of an effect on speed than anything else in the rest of the code
200             $self->[f_next] = sub {
201 33   100 33   553 bless( (
202             [
203             ($rows->[$ct++] or $ct = 0 or return DUMMY ),
204             $buddy # buddy object comes along for the ride - to keep my recmaker in scope
205             ]
206             ), $class );
207 13         80 };
208              
209 13         68 $self->[f_state] = stMEM;
210 13         28 $self->[f_count] = @$rows;
211 13         36 return 1;
212              
213             }
214              
215             sub _fetch_all{
216 3     3   5 my $self = shift;
217              
218 3 50       17 if( $self->[f_state] == stCLEAN ){
219 3         17 $self->_execute;
220             }
221              
222 3 50       18 if( $self->[f_state] == stMEM ){ # This should cover split queries
223              
224 0         0 return $self->[f_rowcache];
225              
226             }else{ # Must be stACTIVE
227              
228 3         18 my $sth = $self->[f_query]->run; # just gets the sth if it's already been run
229              
230 3         99 my $rows = $self->[f_rowcache] = $sth->fetchall_arrayref();
231              
232 3         20 $self->_mem_iterator(); # everything is in memory now, so use _mem_iterator
233              
234 3         13 return $rows;
235             }
236             }
237              
238             ###################################################
239             ### Utility #######################################
240             ###################################################
241              
242             sub count{
243 33     33 0 9046 my $self = shift;
244 33 100       415 return $self->[f_count] if defined $self->[f_count];
245              
246 16 100       73 if( defined $self->[f_splitval] ){ # run automatically if we are a split query
247 6         25 $self->_execute();
248 6         24 return $self->[f_count];
249             }
250              
251 10         72 my $cquery = $self->[f_query]->transpose('Count');
252              
253 10         54 return $self->[f_count] = $cquery->run;
254              
255             # Consider profiling min/max/avg rows returned for the scope in question
256             # IF max / avg is < 1000 just fetch all rows instead of executing another query
257              
258             }
259              
260              
261             sub set {
262 0     0 0 0 my $self = shift;
263 0         0 my %params = @_;
264              
265 0         0 my $tables = $self->[f_query]->tables;
266 0         0 my $table = $tables->[0]; # only the primary table is supported
267 0         0 my $alias = $table->alias;
268              
269 0         0 my @sets;
270 0         0 foreach my $name ( keys %params ){
271 0 0       0 my $field = $table->get_field( $name ) or croak "Invalid field $name";
272 0 0       0 $field->alias( $alias ) if $alias;
273              
274 0 0       0 $field->is_readonly && croak ("Field $name is readonly");
275              
276 0         0 my $value = $field->makevalue( $params{ $name } );
277              
278 0 0       0 $value->count == 1 or croak("Field $name allows only a single value");
279              
280 0 0       0 my $setobj = DBR::Query::Part::Set->new( $field, $value ) or return $self->_error('failed to create set object');
281              
282 0         0 push @sets, $setobj;
283             };
284              
285 0 0       0 scalar(@sets) > 0 or croak('Must specify at least one field to set');
286              
287 0         0 my $update = $self->[f_query]->transpose( 'Update',
288             sets => \@sets
289             );
290 0         0 return $update->run;
291              
292             }
293              
294             sub where {
295 10     10 0 17 my $self = shift;
296              
297 10         84 return DBR::ResultSet->new(
298             $self->[f_query]->child_query( \@_ ), # Where clause
299             $self->[f_splitval],
300             );
301             }
302              
303 0     0 0 0 sub delete { croak "Mass delete is not allowed. No cookie for you!" }
304              
305             # Dunno if I like this
306             sub each {
307 1     1 0 3 my $self = shift;
308 1         2 my $coderef = shift;
309 1         2 my $r;
310 1         6 $coderef->($r) while ($r = $self->[f_next]->( $self ) );
311              
312 1         6 return 1;
313              
314             }
315              
316             # get all instances of a field or fields from the resultset
317             # Kind of a flimsy way to do this, but it's lightweight
318             sub values {
319 1     1 0 422 my $self = shift;
320 1         4 my @fieldnames = grep { /^[A-Za-z0-9_.]+$/ } map { split(/\s+/,$_) } @_;
  1         7  
  1         6  
321              
322 1 50       4 scalar(@fieldnames) or croak('Must provide a list of field names');
323              
324 1         7 my $rows = $self->_fetch_all;
325              
326 1 0       5 return wantarray?():[] unless $self->count > 0;
    50          
327              
328 1         4 my @parts;
329 1         4 foreach my $fieldname (@fieldnames){
330 1         4 $fieldname =~ s/\./->/g; # kind of a hack, but it works
331 1         4 push @parts , "\$_[0]->$fieldname";
332             }
333              
334 1         3 my $code;
335 1 50       4 if(scalar(@fieldnames) > 1){
336 0         0 $code = ' [ ' . join(', ', @parts) . ' ]';
337             }else{
338 1         3 $code = $parts[0];
339             }
340              
341 1         12 $code = 'sub{ push @output, ' . $code . ' }';
342              
343 1         12 $self->[f_query]->_logDebug3($code);
344              
345 1         2 my @output;
346 1         87 my $sub = eval $code;
347 1 50       6 confess "values failed ($@)" if $@;
348              
349 1 50       4 $self->each($sub) or confess "Failed to each";
350              
351 1 50       29 return wantarray?(@output):\@output;
352             }
353              
354 0     0 0 0 sub hashmap_multi { shift->_lookuphash('multi', @_) }
355 2     2 0 20 sub hashmap_single{ shift->_lookuphash('single',@_) }
356              
357             sub _lookuphash{
358 2     2   6 my $self = shift;
359 2         4 my $mode = shift;
360 2         9 my @fieldnames = map { split(/\s+/,$_) } @_;
  2         13  
361              
362 2 50       13 scalar(@fieldnames) or croak('Must provide a list of field names');
363              
364 2         15 my $rows = $self->_fetch_all;
365              
366 2 50       10 return {} unless $self->count > 0;
367              
368 2         10 my $record = $self->[f_query]->get_record_obj;
369 2         11 my $class = $record->class;
370 2         8 my $buddy = [ $self->[f_rowcache], $record ]; # buddy ref must contain the record object just to keep it in scope.
371              
372 2         5 my $code;
373 2         7 foreach my $fieldname (@fieldnames){
374 2         18 my @parts = split(/\.|\->/,$fieldname);
375 2 50       4 map {croak "Invalid fieldname part '$_'" unless /^[A-Za-z0-9_-]+$/} @parts;
  2         24  
376              
377 2         7 $fieldname = join('->',@parts);
378              
379 2         11 $code .= "{ \$_->$fieldname }";
380             }
381 2         5 my $part = ' map { bless([$_,$buddy],$class) } @{$rows}';
382              
383 2 50       8 if($mode eq 'multi'){
384 0         0 $code = 'map { push @{ $lookup' . $code . ' }, $_ }' . $part;
385             }else{
386 2         11 $code = 'map { $lookup' . $code . ' = $_ }' . $part;
387             }
388 2         29 $self->[f_query]->_logDebug3($code);
389              
390 2         4 my %lookup;
391 2         249 eval $code;
392 2 50       15 croak "hashmap_$mode failed ($@)" if $@;
393              
394 2         25 return \%lookup;
395             }
396              
397             1;