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