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