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