File Coverage

blib/lib/DBR/Config/Field.pm
Criterion Covered Total %
statement 130 146 89.0
branch 51 84 60.7
condition 21 39 53.8
subroutine 28 31 90.3
pod 0 21 0.0
total 230 321 71.6


line stmt bran cond sub pod time code
1             # the contents of this file are Copyright (c) 2009 Daniel Norman
2             # This program is free software; you can redistribute it and/or
3             # modify it under the terms of the GNU General Public License as
4             # published by the Free Software Foundation.
5              
6             package DBR::Config::Field;
7              
8 18     18   113 use strict;
  18         34  
  18         755  
9 18     18   94 use base 'DBR::Config::Field::Common';
  18         36  
  18         1485  
10 18     18   108 use Scalar::Util 'looks_like_number';
  18         35  
  18         1116  
11 18     18   11296 use DBR::Query::Part::Value;
  18         52  
  18         609  
12 18     18   146 use DBR::Config::Table;
  18         40  
  18         456  
13 18     18   9630 use DBR::Config::Trans;
  18         294  
  18         736  
14 18     18   211 use Clone;
  18         50  
  18         789  
15 18     18   103 use Carp;
  18         39  
  18         1786  
16              
17             use constant ({
18             # This MUST match the select from dbr_fields verbatim
19 18         70902 C_field_id => 0,
20             C_table_id => 1,
21             C_name => 2,
22             C_data_type => 3,
23              
24             C_is_nullable => 4, # HERE - consider compressing these using bitmask
25             C_is_signed => 5,
26             C_is_pkey => 6,
27              
28             C_trans_id => 7,
29             C_max_value => 8,
30             C_regex => 9,
31             C_default => 10,
32              
33             C_is_readonly => 11, # Not in table
34             C_testsub => 12,
35              
36             # Object fields
37             O_field_id => 0,
38             O_session => 1,
39             O_index => 2,
40             O_table_alias => 3,
41             O_alias_flag => 4,
42 18     18   113 });
  18         39  
43              
44             my %VALCHECKS;
45             my %FIELDS_BY_ID;
46              
47             #This is ugly... clean it up
48             my %datatypes = (
49             bigint => { id => 1, numeric => 1, bits => 64},
50              
51             int => { id => 2, numeric => 1, bits => 32},
52             integer => { id => 2, numeric => 1, bits => 32}, # duplicate
53              
54             mediumint => { id => 3, numeric => 1, bits => 24},
55             smallint => { id => 4, numeric => 1, bits => 16},
56             tinyint => { id => 5, numeric => 1, bits => 8},
57             bool => { id => 6, numeric => 1, bits => 1},
58             boolean => { id => 6, numeric => 1, bits => 1},
59             float => { id => 7, numeric => 1, bits => 'NA'},
60             double => { id => 8, numeric => 1, bits => 'NA'},
61             varchar => { id => 9 },
62             char => { id => 10 },
63             text => { id => 11 },
64             mediumtext=> { id => 12 },
65             blob => { id => 13 },
66             longblob => { id => 14 },
67             mediumblob=> { id => 15 },
68             tinyblob => { id => 16 },
69             enum => { id => 17 }, # I loathe mysql enums
70             decimal => { id => 18, numeric => 1, bits => 'NA'}, # HERE - may need a little more attention for proper range checking
71             datetime => { id => 19 },
72             );
73              
74             my %datatype_lookup = map { $datatypes{$_}->{id} => {%{$datatypes{$_}}, handle => $_ }} keys %datatypes;
75              
76             sub list_datatypes{
77 0     0 0 0 return Clone::clone( [ sort { $a->{id} <=> $b->{id} } values %datatype_lookup ] );
  0         0  
78             }
79              
80             sub get_type_id{
81 172     172 0 337 my( $package ) = shift;
82 172         272 my $type = shift;
83 172   50     1053 my $ref = $datatypes{lc($type)} || return undef;
84              
85 172         12470 return $ref->{id};
86             }
87              
88             sub load{
89 34     34 0 100 my( $package ) = shift;
90 34         144 my %params = @_;
91              
92 34   50     167 my $session = $params{session} || return croak('session is required');
93 34   50     152 my $instance = $params{instance} || return croak('instance is required');
94              
95 34   50     138 my $table_ids = $params{table_id} || return croak('table_id is required');
96 34 50       156 $table_ids = [$table_ids] unless ref($table_ids) eq 'ARRAY';
97              
98 34 50       130 return 1 unless @$table_ids;
99              
100 34   50     146 my $dbrh = $instance->connect || return croak("Failed to connect to ${\$instance->name}");
101              
102 34 50       386 die('Failed to select fields') unless
103             my $fields = $dbrh->select(
104             -table => 'dbr_fields',
105             # This MUST match constants above
106             -fields => 'field_id table_id name data_type is_nullable is_signed is_pkey trans_id max_value regex default_val',
107             -where => { table_id => ['d in',@$table_ids] },
108             -arrayref => 1,
109             );
110              
111              
112 34         18183 my @trans_fids;
113 34         130 foreach my $field (@$fields){
114             # Consider adding another config param: is_readonly
115              
116 344 100       1027 $field->[C_is_readonly] = 1 if $field->[C_is_pkey];
117              
118 344 100 100     2657 DBR::Config::Table->_register_field(
    50          
119             table_id => $field->[C_table_id],
120             name => $field->[C_name],
121             field_id => $field->[C_field_id],
122             is_pkey => $field->[C_is_pkey] ? 1 : 0,
123             is_req => !( $field->[C_is_nullable] || $field->[C_is_pkey] ),
124             # OK OK... this is a hack. Just because it's a pkey doesn't mean it's not required.
125             # It would seem that we need to be aware of serial/trigger fields.
126             ) or die('failed to register field');
127              
128 344 100       1583 if ( $datatype_lookup{ $field->[C_data_type] }->{handle} eq 'datetime' ){
129 18   50     118 $field->[C_trans_id] ||= 5; #DateTime hack
130             }
131              
132 344 50       1277 _gen_valcheck($field) or die('failed to generate value checking routine');
133              
134 344         2180 $FIELDS_BY_ID{ $field->[C_field_id] } = $field;
135 344 100       1846 push @trans_fids, $field->[C_field_id] if $field->[C_trans_id];
136             }
137              
138 34 100       423 if (@trans_fids){
139              
140 24 50       371 DBR::Config::Trans->load(
141             session => $session,
142             instance => $instance,
143             field_id => \@trans_fids,
144             ) or return die('failed to load translators');
145              
146             }
147              
148 34         303 return 1;
149             }
150              
151             sub _gen_valcheck{ # Intentionally Non-oo
152 353     353   661 my $fieldref = shift;
153 353         626 my $dt = $datatype_lookup{ $fieldref->[C_data_type] };
154              
155 353         461 my @code;
156              
157 353 100       2021 if($dt->{numeric}){
158 250         558 push @code, 'looks_like_number($v)';
159              
160 250 100       1647 if($dt->{bits} ne 'NA'){ # can't really range check floats and such things
161 228         2509 my ($min,$max) = (0, 2 ** $dt->{bits});
162              
163 228 50       711 if($fieldref->[C_is_signed]){ $max /= 2; $min = 0 - $max }
  0         0  
  0         0  
164 228         887 push @code, "\$v >= $min", '$v <= ' . ($max - 1);
165             }
166             }else{
167 103 100       383 push @code, 'defined($v)' unless $fieldref->[C_is_nullable];
168 103 100 66     941 if ($fieldref->[C_max_value] =~ /^\d+$/ && $fieldref->[C_max_value] > 0){ # use regex to prevent code injection
169 85         151 my $max = $fieldref->[C_max_value];
170 85         4201 push @code, "length(\$v)<= $max";
171             }
172              
173             }
174              
175 353         827 my $R; # For safety sake, using $R for regex, no direct compilation to avoid code insertion
176 353         667 my $extra = '';
177 353 100 66     1432 if (defined($fieldref->[C_regex]) && length($fieldref->[C_regex])){
178 18         49 $R = $fieldref->[C_regex];
179 18         37 push @code, "\$v =~ /\$R/o"; # supposedly o is only functional for <5.6
180 18         54 $extra .= "\0" . $R; # Use extra to cache based on the contents of the regex
181             }
182            
183 353         929 my $code = join(' && ', @code);
184              
185 353 100 100     2049 $code = "!defined(\$v)||($code)" if length($code) && $fieldref->[C_is_nullable];
186              
187             #print STDERR "VALCHECK:$fieldref->[C_data_type], $code\t$R\n";
188              
189 353   33     15855 $fieldref->[C_testsub] = $VALCHECKS{$code . $extra} ||= eval "sub { my \$v = shift; $code }"
      66        
190             || confess "DBR::Config::Field::_get_valcheck: failed to gen sub '$@'";
191              
192 353         3870 return 1;
193             }
194              
195              
196             ####################################################################################################
197             ####################################################################################################
198             ####################################################################################################
199             ####################################################################################################
200              
201              
202              
203             sub new {
204 472     472 0 1289 my $package = shift;
205 472         1642 my %params = @_;
206              
207             # Order must match O_ constants
208 472         2406 my $self = [$params{field_id}, $params{session}];
209              
210 472         6030 bless( $self, $package );
211              
212 472 50       1749 return $self->_error('field_id is required') unless $self->[O_field_id];
213 472 50       1374 return $self->_error('session is required' ) unless $self->[O_session];
214              
215 472 50       2003 $FIELDS_BY_ID{ $self->[O_field_id] } or return $self->_error('invalid field_id');
216              
217 472         3490 return( $self );
218             }
219              
220             sub clone{
221 275     275 0 444 my $self = shift;
222 275         641 my %params = @_;
223              
224 275 100       3122 return bless(
    50          
225             [
226             $self->[O_field_id],
227             $self->[O_session],
228             $params{with_index} ? $self->[O_index] : undef, # index
229             $params{with_alias} ? $self->[O_table_alias] : undef, #alias
230             ],
231             ref($self),
232             );
233              
234             }
235              
236             sub makevalue{ # shortcut function?
237 150     150 0 298 my $self = shift;
238 150         249 my $value = shift;
239              
240 150         661 return DBR::Query::Part::Value->new(
241             session => $self->[O_session],
242             value => $value,
243             is_number => $self->is_numeric,
244             field => $self,
245             );
246              
247             }
248              
249 522     522 0 6949 sub field_id { $_[0]->[O_field_id] }
250 97     97 0 1521 sub table_id { $FIELDS_BY_ID{ $_[0]->[O_field_id] }->[C_table_id] }
251 467     467 0 5312 sub name { $FIELDS_BY_ID{ $_[0]->[O_field_id] }->[C_name] }
252 103     103 0 530 sub is_pkey { $FIELDS_BY_ID{ $_[0]->[O_field_id] }->[C_is_pkey] }
253 12     12 0 81 sub is_nullable { $FIELDS_BY_ID{ $_[0]->[O_field_id] }->[C_is_nullable] }
254 107     107 0 819 sub is_readonly { $FIELDS_BY_ID{ $_[0]->[O_field_id] }->[C_is_readonly] }
255 0     0 0 0 sub datatype { $FIELDS_BY_ID{ $_[0]->[O_field_id] }->[C_data_type] }
256 158     158 0 1615 sub testsub { $FIELDS_BY_ID{ $_[0]->[O_field_id] }->[C_testsub] }
257 1     1 0 9 sub default_val { $FIELDS_BY_ID{ $_[0]->[O_field_id] }->[C_default] }
258              
259             sub table {
260 46     46 0 310 return DBR::Config::Table->new(
261             session => $_[0][O_session],
262             table_id => $FIELDS_BY_ID{ $_[0][O_field_id] }->[C_table_id]
263             );
264             }
265              
266             sub is_numeric{
267 300     300 0 801 my $field = $FIELDS_BY_ID{ $_[0]->[O_field_id] };
268 300 100       2317 return $datatype_lookup{ $field->[C_data_type] }->{numeric} ? 1:0;
269             }
270              
271             sub translator{
272 263     263 0 534 my $self = shift;
273              
274 263 100       1454 my $trans_id = $FIELDS_BY_ID{ $self->[O_field_id] }->[C_trans_id] or return undef;
275              
276 68         504 return DBR::Config::Trans->new(
277             session => $self->[O_session],
278             field_id => $self->[O_field_id],
279             trans_id => $trans_id,
280             );
281             }
282              
283              
284             ### Admin functions
285              
286             sub update_translator{
287 37     37 0 74 my $self = shift;
288 37         75 my $transname = shift;
289              
290 37 50       281 $self->[O_session]->is_admin or return $self->_error('Cannot update translator in non-admin mode');
291              
292 37         505 my $existing_trans_id = $FIELDS_BY_ID{ $self->[O_field_id] }->[C_trans_id];
293              
294 37 50       321 my $trans_defs = DBR::Config::Trans->list_translators or die 'Failed to get translator list';
295              
296 37         96 my %trans_lookup;
297 37         75 map {$trans_lookup{ uc($_->{name}) } = $_} @$trans_defs;
  185         677  
298 37 50       572 my $new_trans = $trans_lookup{ uc ($transname) } or die "Invalid translator '$transname'";
299              
300 37 50 33     156 return 1 if $existing_trans_id && $new_trans->{id} == $existing_trans_id;
301              
302              
303 37 50       557 my $instance = $self->table->conf_instance or die "Failed to retrieve conf instance";
304 37 50       192 my $dbrh = $instance->connect or die "Failed to connect to conf instance";
305              
306 37 50       421 $dbrh->update(
307             -table => 'dbr_fields',
308             -fields => { trans_id => ['d', $new_trans->{id} ]},
309             -where => { field_id => ['d', $self->field_id ]}
310             ) or die "Failed to update dbr_fields";
311              
312 37         297 $FIELDS_BY_ID{ $self->[O_field_id] }->[C_trans_id] = $new_trans->{id}; # update local copy
313              
314 37         174 return 1;
315             }
316              
317             sub update_regex{
318 9     9 0 21 my $self = shift;
319 9         23 my $regex = shift;
320              
321 9 50       63 $self->[O_session]->is_admin or return $self->_error('Cannot update translator in non-admin mode');
322              
323 9         37 my $existing_regex = $FIELDS_BY_ID{ $self->[O_field_id] }->[C_regex];
324 9 50 33     55 return 1 if defined($existing_regex) && $regex eq $existing_regex;
325              
326 9 50       46 my $instance = $self->table->conf_instance or die "Failed to retrieve conf instance";
327 9 50       59 my $dbrh = $instance->connect or die "Failed to connect to conf instance";
328              
329 9 50       80 $dbrh->update(
330             -table => 'dbr_fields',
331             -fields => { regex => $regex },
332             -where => { field_id => ['d', $self->field_id ]}
333             ) or die "Failed to update dbr_fields";
334              
335 9         59 my $fieldref = $FIELDS_BY_ID{ $self->[O_field_id] };
336 9         29 $fieldref->[C_regex] = $regex; # update local copy
337 9         46 _gen_valcheck($fieldref); # Update value test sub
338              
339 9         46 return 1;
340             }
341              
342             sub update_default{
343 0     0 0   my $self = shift;
344 0           my $value = shift;
345              
346 0 0         $self->[O_session]->is_admin or return $self->_error('Cannot update translator in non-admin mode');
347              
348 0           my $existing_value = $FIELDS_BY_ID{ $self->[O_field_id] }->[C_default];
349 0 0 0       return 1 if defined($existing_value) && $value eq $existing_value;
350              
351 0 0         my $instance = $self->table->conf_instance or die "Failed to retrieve conf instance";
352 0 0         my $dbrh = $instance->connect or die "Failed to connect to conf instance";
353              
354 0 0         $dbrh->update(
355             -table => 'dbr_fields',
356             -fields => { default_val => $value },
357             -where => { field_id => ['d', $self->field_id ]}
358             ) or die "Failed to update dbr_fields";
359              
360 0           my $fieldref = $FIELDS_BY_ID{ $self->[O_field_id] };
361 0           $fieldref->[C_default] = $value; # update local copy
362              
363 0           return 1;
364             }
365              
366             1;