File Coverage

blib/lib/DBR/Record/Helper.pm
Criterion Covered Total %
statement 197 203 97.0
branch 65 120 54.1
condition 16 20 80.0
subroutine 20 20 100.0
pod 0 6 0.0
total 298 369 80.7


line stmt bran cond sub pod time code
1             package DBR::Record::Helper;
2              
3 18     18   104 use strict;
  18         1479  
  18         722  
4 18     18   104 use base 'DBR::Common';
  18         37  
  18         1439  
5 18     18   158 use Carp;
  18         39  
  18         4467  
6 18     18   112 use DBR::Query::Part;
  18         39  
  18         387  
7 18     18   95 use DBR::Query::Select;
  18         37  
  18         527  
8 18     18   8784 use DBR::Query::Update;
  18         49  
  18         510  
9 18     18   9759 use DBR::Query::Delete;
  18         48  
  18         490  
10 18     18   8981 use DBR::ResultSet;
  18         58  
  18         693  
11 18     18   195 use DBR::ResultSet::Empty;
  18         38  
  18         399  
12 18     18   100 use DBR::Misc::Dummy;
  18         40  
  18         684  
13              
14             # we can get away with making these once for all time
15             use constant ({
16 18         67445 EMPTY => bless( [], 'DBR::ResultSet::Empty'),
17             DUMMY => bless( [], 'DBR::Misc::Dummy'),
18 18     18   146 });
  18         38  
19             sub new {
20 27     27 0 105 my( $package ) = shift;
21 27         407 my %params = @_;
22 27         226 my $self = {
23             session => $params{session},
24             instance => $params{instance},
25             tablemap => $params{tablemap},
26             flookup => $params{flookup},
27             pkmap => $params{pkmap},
28             scope => $params{scope},
29             lastidx => $params{lastidx},
30             };
31              
32 27         101 bless( $self, $package ); # BS object
33              
34 27 50       163 $self->{session} or return $self->_error('session is required');
35 27 50       111 $self->{instance} or return $self->_error('instance is required');
36 27 50       97 $self->{scope} or return $self->_error('scope is required');
37              
38 27 50       99 $self->{tablemap} or return $self->_error('tablemap is required');
39 27 50       108 $self->{pkmap} or return $self->_error('pkmap is required'); # X
40 27 50       109 $self->{flookup} or return $self->_error('flookup is required'); # X
41 27 50       101 defined($self->{lastidx}) or return $self->_error('lastidx is required');
42              
43 27         174 return $self;
44             }
45              
46             sub set{
47 4     4 0 9 my $self = shift;
48 4         9 my $record = shift;
49 4         19 my %params = @_;
50              
51 4         8 my %sets;
52 4         18 foreach my $fieldname (keys %params){
53 4 50       23 my $field = $self->{flookup}->{$fieldname} or return $self->_error("$fieldname is not a valid field");
54 4 50       28 $field->is_readonly && return $self->_error("Field $fieldname is readonly");
55              
56 4 50       23 my $setvalue = $field->makevalue($params{$fieldname}) or return $self->_error('failed to create setvalue object');
57 4 50       18 $setvalue->count == 1 or return $self->_error("Field ${\$field->name} allows only a single value");
  0         0  
58              
59 4 50       31 my $setobj = DBR::Query::Part::Set->new( $field, $setvalue ) or return $self->_error('failed to create set object');
60              
61 4         5 push @{$sets{$field->table_id}}, $setobj;
  4         21  
62             }
63 4         12 my $ct = scalar(keys %sets);
64              
65 4 50       35 return $self->_error('Must specify at least one field to set') unless $ct > 0;
66              
67 4         6 my $dbrh;
68 4 50       14 if($ct > 1){
69             # create a new DBRH here to ensure proper transactional handling
70 0 0       0 $dbrh = $self->{instance}->connect or return $self->_error('failed to connect');
71 0         0 $dbrh->begin;
72             }
73              
74 4         13 foreach my $table_id (keys %sets){
75 4 50       19 $self->_set($record, $table_id, $sets{$table_id}) or return $self->_error('failed to set');
76             }
77              
78 4 50       22 $dbrh->commit if $ct > 1;
79              
80 4         67 return 1;
81             }
82              
83             # set a field REGARDLESS of whether it was prefetched or not
84             sub setfield{
85 8     8 0 23 my $self = shift;
86 8         18 my $record = shift;
87 8         17 my $field = shift;
88 8         21 my $value = shift;
89              
90 8 50       59 my $setvalue = $field->makevalue($value) or return $self->_error('failed to create value object');
91 8 50       45 $setvalue->count == 1 or return $self->_error("Value of ${\$field->name} must have only a single value");
  0         0  
92              
93 8 50       66 my $setobj = DBR::Query::Part::Set->new( $field, $setvalue ) or return $self->_error('failed to create set object');
94              
95 8         51 return $self->_set($record, $field->table_id, [$setobj]);
96             }
97              
98             sub _set{
99 12     12   27 my $self = shift;
100 12         26 my $record = shift;
101 12         21 my $table_id = shift;
102 12         20 my $sets = shift;
103              
104 12 50       62 my ($outwhere,$table) = $self->_pk_where($record,$table_id) or return $self->_error('failed to create where tree');
105              
106 12 50       114 my $query = DBR::Query::Update->new(
107             session => $self->{session},
108             instance => $self->{instance},
109             tables => $table,
110             where => $outwhere,
111             sets => $sets
112             ) or return $self->_error('failed to create Query object');
113              
114 12 50       61 my $rv = $query->run() or return $self->_error('failed to execute');
115              
116 12         72 foreach my $set (@$sets){
117 12         107 $self->_setlocalval($record, $set->field, $set->value->raw->[0]);
118             }
119              
120 12         289 return $rv;
121             }
122              
123             sub delete{
124 1     1 0 3 my $self = shift;
125 1         2 my $record = shift;
126              
127 1         7 return $self->_error('Cannot call delete on join record')
128 1 50       2 if scalar(keys %{$self->{tablemap}}) > 1;
129              
130 1         2 my ($table_id) = keys %{$self->{tablemap}};
  1         5  
131              
132 1 50       6 my ($outwhere,$table) = $self->_pk_where($record,$table_id) or return $self->_error('failed to create where tree');
133              
134 1 50       9 my $query = DBR::Query::Delete->new(
135             session => $self->{session},
136             instance => $self->{instance},
137             tables => $table,
138             where => $outwhere,
139             ) or return $self->_error('failed to create Query object');
140              
141 1 50       5 $query->run or return $self->_error('failed to execute');
142              
143 1         24 return 1;
144             }
145              
146              
147             # Fetch a field ONLY if it was not prefetched
148             sub getfield{
149 42     42 0 731 my $self = shift;
150 42         10106 my $record = shift;
151 42         95 my $field = shift;
152 42         74 my $want_sref = shift;
153              
154             # Check to see if we've previously been assigned an index. if so, see if our record already has it fetched
155             # This could happen if the field was not fetched in the master query, but was already fetched with getfield
156 42         444 my $idx = $field->index;
157 42 50 66     14097 return $record->[$idx] if defined($idx) && exists($record->[$idx]);
158              
159 42 50       347 $self->{scope}->addfield($field) or return $self->_error('Failed to add field to scope');
160              
161 42 50       279 my ($outwhere,$table) = $self->_pk_where($record,$field->table_id) or return $self->_error('failed to create where tree');
162              
163             # Because we are doing a new select, which will set the indexes on
164             # its fields, we must clone the field provided by the original query
165 42         213 my $newfield = $field->clone;
166              
167 42 50       429 my $query = DBR::Query::Select->new(
168             session => $self->{session},
169             instance => $self->{instance},
170             tables => $table,
171             where => $outwhere,
172             fields => [ $newfield ] # use the new cloned field
173             ) or return $self->_error('failed to create Query object');
174              
175 42 50       283 my $sth = $query->run or return $self->_error('failed to execute');
176              
177 42 50       5606 $sth->execute() or return $self->_error('Failed to execute sth');
178 42 50       936 my $row = $sth->fetchrow_arrayref() or return $self->_error('Failed to fetchrow');
179              
180 42         222 my $val = $row->[ $newfield->index ];
181              
182 42         224 $self->_setlocalval($record,$field,$val);
183              
184 42 100       295 return $want_sref?\$val:$val; # return a scalarref if requested
185             }
186              
187             sub getrelation{
188 30     30 0 70 my $self = shift;
189 30         57 my $obj = shift;
190 30         51 my $relation = shift;
191 30         66 my $field = shift;
192              
193 30         82 my $record = $obj->[0];
194 30         57 my $buddy = $obj->[1];
195 30         50 my $rowcache = $buddy->[0];
196              
197 30         167 my $ridx = $relation->index;
198             # Check to see if this record has a cached version of the resultset
199 30 100 100     472 return $record->[$ridx] if defined($ridx) && exists($record->[$ridx]); # skip the rest if we have that
200              
201 12         60 my $fidx = $field->index();
202 12         23 my $val;
203              
204 12         135 my $to1 = $relation->is_to_one; # Candidate for pre-processing
205 12 50       103 my $table = $relation->table or return $self->_error('Failed to fetch table' );# Candidate for pre-processing
206 12 50       57 my $maptable = $relation->maptable or return $self->_error('Failed to fetch maptable');# Candidate for pre-processing
207 12 50       74 my $mapfield = $relation->mapfield or return $self->_error('Failed to fetch mapfield');# Candidate for pre-processing
208              
209 12         26 my @allvals; # For uniq-ing
210              
211 12 100 100     99 if( defined($fidx) && exists($record->[$fidx]) ){
212 6         19 $val = $record->[ $fidx ]; # My value
213 6         28 @allvals = $self->_uniq( $val, map { $_->[ $fidx ] } grep {defined} @$rowcache ); # look forward in the rowcache and add those too
  16         92  
  16         42  
214             }else{
215             # I forget, I think I'm using scalar ref as a way to represent undef and still have a true rvalue *ugh*
216 6         36 my $sref = $self->getfield($record,$field, 1 ); # go fetch the value in the form of a scalarref
217 6 50       32 defined ($sref) or return $self->_error("failed to fetch the value of ${\ $field->name }");
  0         0  
218 6         14 $val = $$sref;
219 6   66     40 $fidx ||= $field->index;
220 6 50       19 confess('field object STILL does not have an index') unless defined($fidx);
221 6         18 push @allvals, $val;
222             }
223              
224 12         45 my $rowcount = scalar @allvals; # Cheapest way to get a rowcount is here, before we filter
225              
226 12 50       65 unless($mapfield->is_nullable){ # Candidate for pre-defined global
227 12         35 @allvals = grep { defined } @allvals;
  22         80  
228             }
229              
230 12 100       47 unless(scalar @allvals){
231             # no values? then for sure, the relationship for this record must be empty.
232             # Cache the emptyness so we don't have to repeat this work
233 2 50       13 return $self->_setlocalval( $record, $relation, $to1 ? DUMMY : EMPTY );
234             }
235              
236 10         125 my $value = $mapfield->makevalue( \@allvals );
237 10         88 my $outwhere = DBR::Query::Part::Compare->new( field => $mapfield, value => $value );
238              
239 10 50       69 my $scope = DBR::Config::Scope->new(
240             session => $self->{session},
241             conf_instance => $maptable->conf_instance,
242             extra_ident => $maptable->name,
243             offset => 2, # because getrelation is being called indirectly, look at the scope two levels up
244             ) or return $self->_error('Failed to get calling scope');
245              
246 10 50       6801 my $pk = $maptable->primary_key or return $self->_error('Failed to fetch primary key');
247 10 50       63 my $prefields = $scope->fields or return $self->_error('Failed to determine fields to retrieve');
248              
249 10         27 my %uniq;
250 10         38 my @fields = grep { !$uniq{ $_->field_id }++ } ($mapfield, @$pk, @$prefields );
  25         98  
251              
252 10         37 my $mapinstance = $self->{instance};
253 10 50       71 unless ( $relation->is_same_schema ){
254 0 0       0 $mapinstance = $maptable->schema->get_instance( $mapinstance->class ) or return $self->_error('Failed to retrieve db instance for the maptable');
255             }
256              
257 10         73 $self->_logDebug2( "Relationship from instance " . $self->{instance}->guid . "->" . $mapinstance->guid );
258 10 50       182 my $query = DBR::Query::Select->new(
259             session => $self->{session},
260             instance => $mapinstance,
261             tables => $maptable,
262             where => $outwhere,
263             fields => \@fields,
264             scope => $scope,
265             splitfield => $mapfield
266             ) or return $self->_error('failed to create Query object');
267              
268              
269 10 100       42 if($rowcount > 1){
270 6         13 my $myresult;
271 6 100       21 if($to1){
272 2 50       20 my $resultset = DBR::ResultSet->new( $query ) or croak('Failed to create resultset');
273 2         13 $self->_logDebug2('mapping to individual records');
274 2 50       15 my $resultmap = $resultset->hashmap_single( $mapfield->name ) or return $self->_error('failed to split resultset');
275              
276             # look forward in the rowcache and assign the resultsets for whatever we find
277 2         8 foreach my $row (grep {defined} @$rowcache) {
  6         18  
278 6   100     52 $self->_setlocalval(
279             $row,
280             $relation,
281             $resultmap->{ $row->[$fidx] } || DUMMY
282             );
283             }
284              
285 2   100     32 $myresult = $resultmap->{$val} || DUMMY;
286              
287             }else{
288             # look forward in the rowcache and assign the resultsets for whatever we find
289 4         12 foreach my $row (grep {defined} @$rowcache) {
  10         22  
290 10         52 $self->_setlocalval($row,
291             $relation,
292             DBR::ResultSet->new( $query, $row->[$fidx] )
293             );
294             }
295              
296 4         18 $myresult = DBR::ResultSet->new( $query, $val );
297             }
298              
299 6         22 $self->_setlocalval($record,$relation,$myresult);
300              
301 6         71 return $myresult;
302              
303             }else{
304 4 50       40 my $resultset = DBR::ResultSet->new( $query ) or croak('Failed to create resultset');
305 4         12 my $result = $resultset;
306 4 50       18 if($to1){
307 4         21 $result = $resultset->next;
308             }
309              
310 4         29 $self->_setlocalval($record,$relation,$result);
311              
312 4         85 return $result;
313             }
314             }
315              
316             sub _pk_where{
317 55     55   130 my $self = shift;
318 55         114 my $record = shift;
319 55         115 my $table_id = shift;
320              
321 55   50     326 my $table = $self->{tablemap}->{ $table_id } || return $self->_error('Missing table for table_id ' . $table_id );
322 55   50     269 my $pk = $self->{pkmap}->{ $table_id } || return $self->_error('Missing primary key');
323              
324 55         305 my @and;
325 55         110 foreach my $part (@{ $pk }){
  55         230  
326              
327 55 50       300 my $value = $part->makevalue( $record->[ $part->index ] ) or return $self->_error('failed to create value object');
328 55 50       468 my $outfield = DBR::Query::Part::Compare->new( field => $part, value => $value ) or return $self->_error('failed to create compare object');
329              
330 55         213 push @and, $outfield;
331             }
332              
333              
334 55         758 return (DBR::Query::Part::And->new(@and), $table);
335             }
336              
337             sub _setlocalval{
338 82     82   175 my $self = shift;
339 82         143 my $record = shift;
340 82         130 my $field = shift; # Could also be a relationship object
341 82         146 my $val = shift;
342              
343 82         339 my $idx = $field->index;
344             # update the field object to give it an index if necessary
345 82 100       240 if(!defined $idx){ #Could be 0
346 26         75 $idx = ++$self->{lastidx};
347 26         91 $field->index($idx); # so we'll have it for next time this gets accessed
348             }
349              
350             # Update this record to reflect the new value
351 82         364 return $record->[$idx] = $val;
352             }
353              
354             1;