File Coverage

blib/lib/SQL/Translator/Schema/Field.pm
Criterion Covered Total %
statement 73 74 98.6
branch 27 34 79.4
condition 4 7 57.1
subroutine 20 20 100.0
pod 3 3 100.0
total 127 138 92.0


line stmt bran cond sub pod time code
1             package SQL::Translator::Schema::Field;
2              
3             =pod
4              
5             =head1 NAME
6              
7             SQL::Translator::Schema::Field - SQL::Translator field object
8              
9             =head1 SYNOPSIS
10              
11             use SQL::Translator::Schema::Field;
12             my $field = SQL::Translator::Schema::Field->new(
13             name => 'foo',
14             table => $table,
15             );
16              
17             =head1 DESCRIPTION
18              
19             C is the field object.
20              
21             =head1 METHODS
22              
23             =cut
24              
25 76     76   2209 use Moo;
  76         2184  
  76         974  
26 76     76   39893 use SQL::Translator::Schema::Constants;
  76         338  
  76         8693  
27 76     76   944 use SQL::Translator::Types qw(schema_obj);
  76         433  
  76         24509  
28 76     76   585 use SQL::Translator::Utils qw(parse_list_arg ex2err throw carp_ro);
  76         244  
  76         6392  
29 76     76   515 use Sub::Quote qw(quote_sub);
  76         182  
  76         4176  
30 76     76   534 use Scalar::Util ();
  76         247  
  76         9231  
31              
32             extends 'SQL::Translator::Schema::Object';
33              
34             our $VERSION = '1.66';
35              
36             # Stringify to our name, being careful not to pass any args through so we don't
37             # accidentally set it to undef. We also have to tweak bool so the object is
38             # still true when it doesn't have a name (which shouldn't happen!).
39             use overload
40 4482     4482   140263 '""' => sub { shift->name },
41 17738 50   17738   762632 'bool' => sub { $_[0]->name || $_[0] },
42 76         1215 fallback => 1,
43 76     76   584 ;
  76         201  
44              
45 76     76   131408 use DBI qw(:sql_types);
  76         1778308  
  76         267890  
46              
47             # Mapping from string to sql constant
48             our %type_mapping = (
49             integer => SQL_INTEGER,
50             int => SQL_INTEGER,
51              
52             tinyint => SQL_TINYINT,
53             smallint => SQL_SMALLINT,
54             bigint => SQL_BIGINT,
55              
56             double => SQL_DOUBLE,
57             'double precision' => SQL_DOUBLE,
58              
59             decimal => SQL_DECIMAL,
60             dec => SQL_DECIMAL,
61             numeric => SQL_NUMERIC,
62              
63             real => SQL_REAL,
64             float => SQL_FLOAT,
65              
66             bit => SQL_BIT,
67              
68             date => SQL_DATE,
69             datetime => SQL_DATETIME,
70             timestamp => SQL_TIMESTAMP,
71             time => SQL_TIME,
72              
73             char => SQL_CHAR,
74             varchar => SQL_VARCHAR,
75             binary => SQL_BINARY,
76             varbinary => SQL_VARBINARY,
77             tinyblob => SQL_BLOB,
78             blob => SQL_BLOB,
79             text => SQL_LONGVARCHAR
80              
81             );
82              
83             has _numeric_sql_data_types => (is => 'lazy');
84              
85             sub _build__numeric_sql_data_types {
86             return {
87 206     206   4149 map { $_ => 1 } (
  1854         10748  
88             SQL_INTEGER, SQL_TINYINT, SQL_SMALLINT, SQL_BIGINT, SQL_DOUBLE, SQL_NUMERIC, SQL_DECIMAL, SQL_FLOAT, SQL_REAL
89             )
90             };
91             }
92              
93             =head2 new
94              
95             Object constructor.
96              
97             my $field = SQL::Translator::Schema::Field->new(
98             name => 'foo',
99             table => $table,
100             );
101              
102             =head2 comments
103              
104             Get or set the comments on a field. May be called several times to
105             set and it will accumulate the comments. Called in an array context,
106             returns each comment individually; called in a scalar context, returns
107             all the comments joined on newlines.
108              
109             $field->comments('foo');
110             $field->comments('bar');
111             print join( ', ', $field->comments ); # prints "foo, bar"
112              
113             =cut
114              
115             has comments => (
116             is => 'rw',
117             coerce => quote_sub(q{ ref($_[0]) eq 'ARRAY' ? $_[0] : [$_[0]] }),
118             default => quote_sub(q{ [] }),
119             );
120              
121             around comments => sub {
122             my $orig = shift;
123             my $self = shift;
124              
125             for my $arg (@_) {
126             $arg = $arg->[0] if ref $arg;
127             push @{ $self->$orig }, $arg if $arg;
128             }
129              
130             return wantarray
131             ? @{ $self->$orig }
132             : join("\n", @{ $self->$orig });
133             };
134              
135             =head2 data_type
136              
137             Get or set the field's data type.
138              
139             my $data_type = $field->data_type('integer');
140              
141             =cut
142              
143             has data_type => (is => 'rw', default => quote_sub(q{ '' }));
144              
145             =head2 sql_data_type
146              
147             Constant from DBI package representing this data type. See L
148             for more details.
149              
150             =cut
151              
152             has sql_data_type => (is => 'rw', lazy => 1, builder => 1);
153              
154             sub _build_sql_data_type {
155 327 100   327   8192 $type_mapping{ lc $_[0]->data_type } || SQL_UNKNOWN_TYPE;
156             }
157              
158             =head2 default_value
159              
160             Get or set the field's default value. Will return undef if not defined
161             and could return the empty string (it's a valid default value), so don't
162             assume an error like other methods.
163              
164             my $default = $field->default_value('foo');
165              
166             =cut
167              
168             has default_value => (is => 'rw');
169              
170             =head2 foreign_key_reference
171              
172             Get or set the field's foreign key reference;
173              
174             my $constraint = $field->foreign_key_reference( $constraint );
175              
176             =cut
177              
178             has foreign_key_reference => (
179             is => 'rw',
180             predicate => '_has_foreign_key_reference',
181             isa => schema_obj('Constraint'),
182             weak_ref => 1,
183             );
184              
185             around foreign_key_reference => sub {
186             my $orig = shift;
187             my $self = shift;
188              
189             if (my $arg = shift) {
190             return $self->error('Foreign key reference for ', $self->name, 'already defined')
191             if $self->_has_foreign_key_reference;
192              
193             return ex2err($orig, $self, $arg);
194             }
195             $self->$orig;
196             };
197              
198             =head2 is_auto_increment
199              
200             Get or set the field's C attribute.
201              
202             my $is_auto = $field->is_auto_increment(1);
203              
204             =cut
205              
206             has is_auto_increment => (
207             is => 'rw',
208             coerce => quote_sub(q{ $_[0] ? 1 : 0 }),
209             builder => 1,
210             lazy => 1,
211             );
212              
213             sub _build_is_auto_increment {
214 714     714   20523 my ($self) = @_;
215              
216 714 100       18333 if (my $table = $self->table) {
217 709 100       36145 if (my $schema = $table->schema) {
218 687 100 66     20574 if ( $schema->database eq 'PostgreSQL'
219             && $self->data_type eq 'serial') {
220 1         33 return 1;
221             }
222             }
223             }
224 713         18759 return 0;
225             }
226              
227             =head2 is_foreign_key
228              
229             Returns whether or not the field is a foreign key.
230              
231             my $is_fk = $field->is_foreign_key;
232              
233             =cut
234              
235             has is_foreign_key => (
236             is => 'rw',
237             coerce => quote_sub(q{ $_[0] ? 1 : 0 }),
238             builder => 1,
239             lazy => 1,
240             );
241              
242             sub _build_is_foreign_key {
243 175     175   19412 my ($self) = @_;
244              
245 175 50       4640 if (my $table = $self->table) {
246 175         5154 for my $c ($table->get_constraints) {
247 369 100       15806 if ($c->type eq FOREIGN_KEY) {
248 93         2523 my %fields = map { $_, 1 } $c->fields;
  93         625  
249 93 100       4583 if ($fields{ $self->name }) {
250 14         657 $self->foreign_key_reference($c);
251 14         699 return 1;
252             }
253             }
254             }
255             }
256 161         7828 return 0;
257             }
258              
259             =head2 is_nullable
260              
261             Get or set whether the field can be null. If not defined, then
262             returns "1" (assumes the field can be null). The argument is evaluated
263             by Perl for True or False, so the following are equivalent:
264              
265             $is_nullable = $field->is_nullable(0);
266             $is_nullable = $field->is_nullable('');
267             $is_nullable = $field->is_nullable('0');
268              
269             While this is technically a field constraint, it's probably easier to
270             represent this as an attribute of the field. In order keep things
271             consistent, any other constraint on the field (unique, primary, and
272             foreign keys; checks) are represented as table constraints.
273              
274             =cut
275              
276             has is_nullable => (
277             is => 'rw',
278             coerce => quote_sub(q{ $_[0] ? 1 : 0 }),
279             default => quote_sub(q{ 1 }),
280             );
281              
282             around is_nullable => sub {
283             my ($orig, $self, $arg) = @_;
284              
285             $self->$orig($self->is_primary_key ? 0 : defined $arg ? $arg : ());
286             };
287              
288             =head2 is_primary_key
289              
290             Get or set the field's C attribute. Does not create
291             a table constraint (should it?).
292              
293             my $is_pk = $field->is_primary_key(1);
294              
295             =cut
296              
297             has is_primary_key => (
298             is => 'rw',
299             coerce => quote_sub(q{ $_[0] ? 1 : 0 }),
300             lazy => 1,
301             builder => 1,
302             );
303              
304             sub _build_is_primary_key {
305 675     675   15884 my ($self) = @_;
306              
307 675 100       17865 if (my $table = $self->table) {
308 673 100       19236 if (my $pk = $table->primary_key) {
309 542         19101 my %fields = map { $_, 1 } $pk->fields;
  579         2878  
310 542   50     29316 return $fields{ $self->name } || 0;
311             }
312             }
313 133         3420 return 0;
314             }
315              
316             =head2 is_unique
317              
318             Determine whether the field has a UNIQUE constraint or not.
319              
320             my $is_unique = $field->is_unique;
321              
322             =cut
323              
324             has is_unique => (is => 'lazy', init_arg => undef);
325              
326             around is_unique => carp_ro('is_unique');
327              
328             sub _build_is_unique {
329 90     90   784 my ($self) = @_;
330              
331 90 50       1814 if (my $table = $self->table) {
332 90         1788 for my $c ($table->get_constraints) {
333 202 100       6778 if ($c->type eq UNIQUE) {
334 77         1765 my %fields = map { $_, 1 } $c->fields;
  77         315  
335 77 100       3062 if ($fields{ $self->name }) {
336 10         376 return 1;
337             }
338             }
339             }
340             }
341 80         2504 return 0;
342             }
343              
344             sub is_valid {
345              
346             =pod
347              
348             =head2 is_valid
349              
350             Determine whether the field is valid or not.
351              
352             my $ok = $field->is_valid;
353              
354             =cut
355              
356 320     320 1 734 my $self = shift;
357 320 50       6427 return $self->error('No name') unless $self->name;
358 320 50       6743 return $self->error('No data type') unless $self->data_type;
359 320 50       6125 return $self->error('No table object') unless $self->table;
360 320         6459 return 1;
361             }
362              
363             =head2 name
364              
365             Get or set the field's name.
366              
367             my $name = $field->name('foo');
368              
369             The field object will also stringify to its name.
370              
371             my $setter_name = "set_$field";
372              
373             Errors ("No field name") if you try to set a blank name.
374              
375             =cut
376              
377             has name => (is => 'rw', isa => sub { throw("No field name") unless $_[0] });
378              
379             around name => sub {
380             my $orig = shift;
381             my $self = shift;
382              
383             if (my ($arg) = @_) {
384             if (my $schema = $self->table) {
385             return $self->error(qq[Can't use field name "$arg": field exists])
386             if $schema->get_field($arg);
387             }
388             }
389              
390             return ex2err($orig, $self, @_);
391             };
392              
393             sub full_name {
394              
395             =head2 full_name
396              
397             Read only method to return the fields name with its table name pre-pended.
398             e.g. "person.foo".
399              
400             =cut
401              
402 1     1 1 603 my $self = shift;
403 1         27 return $self->table . "." . $self->name;
404             }
405              
406             =head2 order
407              
408             Get or set the field's order.
409              
410             my $order = $field->order(3);
411              
412             =cut
413              
414             has order => (is => 'rw', default => quote_sub(q{ 0 }));
415              
416             around order => sub {
417             my ($orig, $self, $arg) = @_;
418              
419             if (defined $arg && $arg =~ /^\d+$/) {
420             return $self->$orig($arg);
421             }
422              
423             return $self->$orig;
424             };
425              
426             sub schema {
427              
428             =head2 schema
429              
430             Shortcut to get the fields schema ($field->table->schema) or undef if it
431             doesn't have one.
432              
433             my $schema = $field->schema;
434              
435             =cut
436              
437 1     1 1 333 my $self = shift;
438 1 50 50     25 if (my $table = $self->table) { return $table->schema || undef; }
  1         27  
439 0         0 return undef;
440             }
441              
442             =head2 size
443              
444             Get or set the field's size. Accepts a string, array or arrayref of
445             numbers and returns a string.
446              
447             $field->size( 30 );
448             $field->size( [ 255 ] );
449             $size = $field->size( 10, 2 );
450             print $size; # prints "10,2"
451              
452             $size = $field->size( '10, 2' );
453             print $size; # prints "10,2"
454              
455             =cut
456              
457             has size => (
458             is => 'rw',
459             default => quote_sub(q{ [0] }),
460             coerce => sub {
461             my @sizes = grep { defined && m/^\d+(?:\.\d+)?$/ } @{ parse_list_arg($_[0]) };
462             @sizes ? \@sizes : [0];
463             },
464             );
465              
466             around size => sub {
467             my $orig = shift;
468             my $self = shift;
469             my $numbers = parse_list_arg(@_);
470              
471             if (@$numbers) {
472             my @new;
473             for my $num (@$numbers) {
474             if (defined $num && $num =~ m/^\d+(?:\.\d+)?$/) {
475             push @new, $num;
476             }
477             }
478             $self->$orig(\@new) if @new; # only set if all OK
479             }
480              
481             return wantarray
482             ? @{ $self->$orig || [0] }
483             : join(',', @{ $self->$orig || [0] });
484             };
485              
486             =head2 table
487              
488             Get or set the field's table object. As the table object stringifies this can
489             also be used to get the table name.
490              
491             my $table = $field->table;
492             print "Table name: $table";
493              
494             =cut
495              
496             has table => (is => 'rw', isa => schema_obj('Table'), weak_ref => 1);
497              
498             around table => \&ex2err;
499              
500             =head2 parsed_field
501              
502             Returns the field exactly as the parser found it
503              
504             =cut
505              
506             has parsed_field => (is => 'rw');
507              
508             around parsed_field => sub {
509             my $orig = shift;
510             my $self = shift;
511              
512             return $self->$orig(@_) || $self;
513             };
514              
515             =head2 equals
516              
517             Determines if this field is the same as another
518              
519             my $isIdentical = $field1->equals( $field2 );
520              
521             =cut
522              
523             around equals => sub {
524             my $orig = shift;
525             my $self = shift;
526             my $other = shift;
527             my $case_insensitive = shift;
528              
529             return 0 unless $self->$orig($other);
530             return 0
531             unless $case_insensitive
532             ? uc($self->name) eq uc($other->name)
533             : $self->name eq $other->name;
534              
535             # Comparing types: use sql_data_type if both are not 0. Else use string data_type
536             if ($self->sql_data_type && $other->sql_data_type) {
537             return 0 unless $self->sql_data_type == $other->sql_data_type;
538             } else {
539             return 0 unless lc($self->data_type) eq lc($other->data_type);
540             }
541              
542             return 0 unless $self->size eq $other->size;
543              
544             {
545             my $lhs = $self->default_value;
546             $lhs = \'NULL' unless defined $lhs;
547             my $lhs_is_ref = !!ref $lhs;
548              
549             my $rhs = $other->default_value;
550             $rhs = \'NULL' unless defined $rhs;
551             my $rhs_is_ref = !!ref $rhs;
552              
553             # If only one is a ref, fail. -- rjbs, 2008-12-02
554             return 0 if $lhs_is_ref xor $rhs_is_ref;
555              
556             my $effective_lhs = $lhs_is_ref ? $$lhs : $lhs;
557             my $effective_rhs = $rhs_is_ref ? $$rhs : $rhs;
558              
559             if ( $self->_is_numeric_data_type
560             && Scalar::Util::looks_like_number($effective_lhs)
561             && Scalar::Util::looks_like_number($effective_rhs)) {
562             return 0 if ($effective_lhs + 0) != ($effective_rhs + 0);
563             } else {
564             return 0 if $effective_lhs ne $effective_rhs;
565             }
566             }
567              
568             return 0 unless $self->is_nullable eq $other->is_nullable;
569              
570             # return 0 unless $self->is_unique eq $other->is_unique;
571             return 0 unless $self->is_primary_key eq $other->is_primary_key;
572              
573             # return 0 unless $self->is_foreign_key eq $other->is_foreign_key;
574             return 0 unless $self->is_auto_increment eq $other->is_auto_increment;
575              
576             # return 0 unless $self->comments eq $other->comments;
577             return 0
578             unless $self->_compare_objects(scalar $self->extra, scalar $other->extra);
579             return 1;
580             };
581              
582             # Must come after all 'has' declarations
583             around new => \&ex2err;
584              
585             sub _is_numeric_data_type {
586 311     311   535 my $self = shift;
587 311         6879 return $self->_numeric_sql_data_types->{ $self->sql_data_type };
588             }
589              
590             1;
591              
592             =pod
593              
594             =head1 AUTHOR
595              
596             Ken Youens-Clark Ekclark@cpan.orgE.
597              
598             =cut