Remove pointless DESTROY methods
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Schema / Constraint.pm
CommitLineData
3c5de62a 1package SQL::Translator::Schema::Constraint;
2
3c5de62a 3=pod
4
5=head1 NAME
6
7SQL::Translator::Schema::Constraint - SQL::Translator constraint object
8
9=head1 SYNOPSIS
10
11 use SQL::Translator::Schema::Constraint;
12 my $constraint = SQL::Translator::Schema::Constraint->new(
13 name => 'foo',
14 fields => [ id ],
43b9dc7a 15 type => PRIMARY_KEY,
3c5de62a 16 );
17
18=head1 DESCRIPTION
19
20C<SQL::Translator::Schema::Constraint> is the constraint object.
21
22=head1 METHODS
23
24=cut
25
96a5759e 26use Moo;
43b9dc7a 27use SQL::Translator::Schema::Constants;
96a5759e 28use SQL::Translator::Utils qw(parse_list_arg ex2err throw);
29use SQL::Translator::Types qw(schema_obj);
30use List::MoreUtils qw(uniq);
31
32with qw(
46ad748f 33 SQL::Translator::Schema::Role::BuildArgs
96a5759e 34 SQL::Translator::Schema::Role::Extra
35 SQL::Translator::Schema::Role::Error
36 SQL::Translator::Schema::Role::Compare
37);
b6a880d1 38
0c04c5a2 39our $VERSION = '1.59';
3c5de62a 40
695c2da2 41my %VALID_CONSTRAINT_TYPE = (
43b9dc7a 42 PRIMARY_KEY, 1,
43 UNIQUE, 1,
44 CHECK_C, 1,
45 FOREIGN_KEY, 1,
695c2da2 46 NOT_NULL, 1,
47);
3c5de62a 48
3c5de62a 49=head2 new
50
51Object constructor.
52
53 my $schema = SQL::Translator::Schema::Constraint->new(
dedb8f3b 54 table => $table, # table to which it belongs
3c5de62a 55 type => 'foreign_key', # type of table constraint
dedb8f3b 56 name => 'fk_phone_id', # name of the constraint
57 fields => 'phone_id', # field in the referring table
65157eda 58 reference_fields => 'phone_id', # referenced field
59 reference_table => 'phone', # referenced table
3c5de62a 60 match_type => 'full', # how to match
2d034ab4 61 on_delete => 'cascade', # what to do on deletes
62 on_update => '', # what to do on updates
3c5de62a 63 );
64
65=cut
66
96a5759e 67# Override to remove empty arrays from args.
68# t/14postgres-parser breaks without this.
69around BUILDARGS => sub {
70 my $orig = shift;
9371be50 71 my $self = shift;
96a5759e 72 my $args = $self->$orig(@_);
43b9dc7a 73
96a5759e 74 foreach my $arg (keys %{$args}) {
46ad748f 75 delete $args->{$arg} if ref($args->{$arg}) eq "ARRAY" && !@{$args->{$arg}};
96a5759e 76 }
77 if (exists $args->{fields}) {
78 $args->{field_names} = delete $args->{fields};
79 }
80 return $args;
81};
43b9dc7a 82
83=head2 deferrable
84
2d034ab4 85Get or set whether the constraint is deferrable. If not defined,
43b9dc7a 86then returns "1." The argument is evaluated by Perl for True or
87False, so the following are eqivalent:
88
89 $deferrable = $field->deferrable(0);
90 $deferrable = $field->deferrable('');
91 $deferrable = $field->deferrable('0');
92
93=cut
94
96a5759e 95has deferrable => ( is => 'rw', coerce => sub { $_[0] ? 1 : 0 }, default => sub { 1 } );
43b9dc7a 96
97=head2 expression
98
99Gets and set the expression used in a CHECK constraint.
100
101 my $expression = $constraint->expression('...');
102
103=cut
104
96a5759e 105has expression => ( is => 'rw', default => sub { '' } );
43b9dc7a 106
96a5759e 107around expression => sub {
108 my ($orig, $self, $arg) = @_;
109 $self->$orig($arg || ());
110};
43b9dc7a 111
43b9dc7a 112sub is_valid {
113
114=pod
115
116=head2 is_valid
117
118Determine whether the constraint is valid or not.
119
120 my $ok = $constraint->is_valid;
121
122=cut
123
124 my $self = shift;
125 my $type = $self->type or return $self->error('No type');
126 my $table = $self->table or return $self->error('No table');
127 my @fields = $self->fields or return $self->error('No fields');
128 my $table_name = $table->name or return $self->error('No table name');
129
130 for my $f ( @fields ) {
131 next if $table->get_field( $f );
132 return $self->error(
133 "Constraint references non-existent field '$f' ",
134 "in table '$table_name'"
135 );
136 }
137
138 my $schema = $table->schema or return $self->error(
139 'Table ', $table->name, ' has no schema object'
140 );
141
142 if ( $type eq FOREIGN_KEY ) {
143 return $self->error('Only one field allowed for foreign key')
144 if scalar @fields > 1;
145
ea93df61 146 my $ref_table_name = $self->reference_table or
43b9dc7a 147 return $self->error('No reference table');
148
149 my $ref_table = $schema->get_table( $ref_table_name ) or
150 return $self->error("No table named '$ref_table_name' in schema");
151
152 my @ref_fields = $self->reference_fields or return;
153
154 return $self->error('Only one field allowed for foreign key reference')
155 if scalar @ref_fields > 1;
156
157 for my $ref_field ( @ref_fields ) {
158 next if $ref_table->get_field( $ref_field );
159 return $self->error(
96a5759e 160 "Constraint from field(s) ".
161 join(', ', map {qq['$table_name.$_']} @fields).
43b9dc7a 162 " to non-existent field '$ref_table_name.$ref_field'"
163 );
164 }
165 }
166 elsif ( $type eq CHECK_C ) {
ea93df61 167 return $self->error('No expression for CHECK') unless
43b9dc7a 168 $self->expression;
169 }
170
171 return 1;
172}
173
3c5de62a 174=head2 fields
175
43b9dc7a 176Gets and set the fields the constraint is on. Accepts a string, list or
177arrayref; returns an array or array reference. Will unique the field
178names and keep them in order by the first occurrence of a field name.
179
ac095e5e 180The fields are returned as Field objects if they exist or as plain
181names if not. (If you just want the names and want to avoid the Field's overload
182magic use L<field_names>).
183
184Returns undef or an empty list if the constraint has no fields set.
185
43b9dc7a 186 $constraint->fields('id');
187 $constraint->fields('id', 'name');
188 $constraint->fields( 'id, name' );
189 $constraint->fields( [ 'id', 'name' ] );
190 $constraint->fields( qw[ id name ] );
3c5de62a 191
43b9dc7a 192 my @fields = $constraint->fields;
3c5de62a 193
194=cut
195
96a5759e 196sub fields {
197 my $self = shift;
198 my $table = $self->table;
199 my @tables = map { $table->get_field($_) || $_ } @{$self->field_names(@_) || []};
200 return wantarray ? @tables
201 : @tables ? \@tables
202 : undef;
3c5de62a 203}
204
ac095e5e 205=head2 field_names
206
207Read-only method to return a list or array ref of the field names. Returns undef
10f70490 208or an empty list if the constraint has no fields set. Useful if you want to
ac095e5e 209avoid the overload magic of the Field objects returned by the fields method.
210
211 my @names = $constraint->field_names;
212
213=cut
214
96a5759e 215has field_names => (
216 is => 'rw',
217 default => sub { [] },
218 coerce => sub { [uniq @{parse_list_arg($_[0])}] },
219);
ac095e5e 220
96a5759e 221around field_names => sub {
222 my $orig = shift;
223 my $self = shift;
224 my $fields = parse_list_arg( @_ );
225 $self->$orig($fields) if @$fields;
752608d5 226
96a5759e 227 $fields = $self->$orig;
228 return wantarray ? @{$fields}
229 : @{$fields} ? $fields
230 : undef;
231};
752608d5 232
233=head2 match_type
234
235Get or set the constraint's match_type. Only valid values are "full"
840447a5 236"partial" and "simple"
752608d5 237
238 my $match_type = $constraint->match_type('FULL');
239
240=cut
241
96a5759e 242has match_type => (
243 is => 'rw',
244 default => sub { '' },
245 coerce => sub { lc $_[0] },
246 isa => sub {
247 my $arg = $_[0];
248 throw("Invalid match type: $arg")
249 if $arg && !($arg eq 'full' || $arg eq 'partial' || $arg eq 'simple');
250 },
251);
752608d5 252
96a5759e 253around match_type => \&ex2err;
3c5de62a 254
255=head2 name
256
257Get or set the constraint's name.
258
259 my $name = $constraint->name('foo');
260
261=cut
262
96a5759e 263has name => ( is => 'rw', default => sub { '' } );
3c5de62a 264
96a5759e 265around name => sub {
266 my ($orig, $self, $arg) = @_;
267 $self->$orig($arg || ());
268};
dedb8f3b 269
270=head2 options
271
ea93df61 272Gets or adds to the constraints's options (e.g., "INITIALLY IMMEDIATE").
dedb8f3b 273Returns an array or array reference.
274
275 $constraint->options('NORELY');
276 my @options = $constraint->options;
277
278=cut
279
96a5759e 280has options => ( is => 'rw', coerce => \&parse_list_arg, default => sub { [] } );
281
282around options => sub {
283 my $orig = shift;
dedb8f3b 284 my $self = shift;
285 my $options = parse_list_arg( @_ );
286
96a5759e 287 push @{ $self->$orig }, @$options;
43b9dc7a 288
96a5759e 289 return wantarray ? @{ $self->$orig } : $self->$orig;
290};
43b9dc7a 291
292=head2 on_delete
293
294Get or set the constraint's "on delete" action.
295
296 my $action = $constraint->on_delete('cascade');
297
298=cut
299
96a5759e 300has on_delete => ( is => 'rw', default => sub { '' } );
43b9dc7a 301
96a5759e 302around on_delete => sub {
303 my ($orig, $self, $arg) = @_;
304 $self->$orig($arg || ());
305};
43b9dc7a 306
307=head2 on_update
308
309Get or set the constraint's "on update" action.
310
311 my $action = $constraint->on_update('no action');
312
313=cut
314
96a5759e 315has on_update => ( is => 'rw', default => sub { '' } );
43b9dc7a 316
96a5759e 317around on_update => sub {
318 my ($orig, $self, $arg) = @_;
319 $self->$orig($arg || ());
320};
43b9dc7a 321
322=head2 reference_fields
323
324Gets and set the fields in the referred table. Accepts a string, list or
325arrayref; returns an array or array reference.
326
327 $constraint->reference_fields('id');
328 $constraint->reference_fields('id', 'name');
329 $constraint->reference_fields( 'id, name' );
330 $constraint->reference_fields( [ 'id', 'name' ] );
331 $constraint->reference_fields( qw[ id name ] );
332
333 my @reference_fields = $constraint->reference_fields;
334
335=cut
336
96a5759e 337has reference_fields => (
338 is => 'rw',
339 coerce => sub { [uniq @{parse_list_arg($_[0])}] },
340 builder => 1,
341 lazy => 1,
342);
343
344around reference_fields => sub {
345 my $orig = shift;
43b9dc7a 346 my $self = shift;
752608d5 347 my $fields = parse_list_arg( @_ );
96a5759e 348 $self->$orig($fields) if @$fields;
43b9dc7a 349
96a5759e 350 $fields = ex2err($orig, $self) or return;
351 return wantarray ? @{$fields} : $fields
352};
43b9dc7a 353
96a5759e 354sub _build_reference_fields {
355 my ($self) = @_;
43b9dc7a 356
96a5759e 357 my $table = $self->table or throw('No table');
358 my $schema = $table->schema or throw('No schema');
359 if ( my $ref_table_name = $self->reference_table ) {
360 my $ref_table = $schema->get_table( $ref_table_name ) or
361 throw("Can't find table '$ref_table_name'");
362
363 if ( my $constraint = $ref_table->primary_key ) {
364 return [ $constraint->fields ];
365 }
366 else {
367 throw(
368 'No reference fields defined and cannot find primary key in ',
369 "reference table '$ref_table_name'"
370 );
371 }
43b9dc7a 372 }
373}
374
43b9dc7a 375=head2 reference_table
376
377Get or set the table referred to by the constraint.
378
379 my $reference_table = $constraint->reference_table('foo');
380
381=cut
382
96a5759e 383has reference_table => ( is => 'rw', default => sub { '' } );
3c5de62a 384
43b9dc7a 385=head2 table
3c5de62a 386
2d034ab4 387Get or set the constraint's table object.
3c5de62a 388
43b9dc7a 389 my $table = $field->table;
3c5de62a 390
391=cut
392
96a5759e 393has table => ( is => 'rw', isa => schema_obj('Table') );
43b9dc7a 394
96a5759e 395around table => \&ex2err;
43b9dc7a 396
dedb8f3b 397=head2 type
43b9dc7a 398
dedb8f3b 399Get or set the constraint's type.
43b9dc7a 400
dedb8f3b 401 my $type = $constraint->type( PRIMARY_KEY );
43b9dc7a 402
403=cut
404
96a5759e 405has type => (
406 is => 'rw',
407 default => sub { '' },
408 isa => sub {
409 throw("Invalid constraint type: $_[0]")
410 if $_[0] && !$VALID_CONSTRAINT_TYPE{ $_[0] };
411 },
412 coerce => sub { (my $t = $_[0]) =~ s/_/ /g; uc $t },
413);
abf315bb 414
96a5759e 415around type => \&ex2err;
abf315bb 416
417=head2 equals
418
419Determines if this constraint is the same as another
420
421 my $isIdentical = $constraint1->equals( $constraint2 );
422
423=cut
424
96a5759e 425around equals => sub {
426 my $orig = shift;
abf315bb 427 my $self = shift;
428 my $other = shift;
429 my $case_insensitive = shift;
d990d84b 430 my $ignore_constraint_names = shift;
ea93df61 431
96a5759e 432 return 0 unless $self->$orig($other);
b8d24485 433 return 0 unless $self->type eq $other->type;
d990d84b 434 unless ($ignore_constraint_names) {
435 return 0 unless $case_insensitive ? uc($self->name) eq uc($other->name) : $self->name eq $other->name;
436 }
abf315bb 437 return 0 unless $self->deferrable eq $other->deferrable;
4598b71c 438 #return 0 unless $self->is_valid eq $other->is_valid;
abf315bb 439 return 0 unless $case_insensitive ? uc($self->table->name) eq uc($other->table->name)
ea93df61 440 : $self->table->name eq $other->table->name;
abf315bb 441 return 0 unless $self->expression eq $other->expression;
ea93df61 442
6a0f3000 443 # Check fields, regardless of order
ea93df61 444 my %otherFields = (); # create a hash of the other fields
6a0f3000 445 foreach my $otherField ($other->fields) {
ea93df61 446 $otherField = uc($otherField) if $case_insensitive;
447 $otherFields{$otherField} = 1;
6a0f3000 448 }
449 foreach my $selfField ($self->fields) { # check for self fields in hash
ea93df61 450 $selfField = uc($selfField) if $case_insensitive;
451 return 0 unless $otherFields{$selfField};
452 delete $otherFields{$selfField};
6a0f3000 453 }
454 # Check all other fields were accounted for
455 return 0 unless keys %otherFields == 0;
456
457 # Check reference fields, regardless of order
ea93df61 458 my %otherRefFields = (); # create a hash of the other reference fields
6a0f3000 459 foreach my $otherRefField ($other->reference_fields) {
ea93df61 460 $otherRefField = uc($otherRefField) if $case_insensitive;
461 $otherRefFields{$otherRefField} = 1;
6a0f3000 462 }
463 foreach my $selfRefField ($self->reference_fields) { # check for self reference fields in hash
ea93df61 464 $selfRefField = uc($selfRefField) if $case_insensitive;
465 return 0 unless $otherRefFields{$selfRefField};
466 delete $otherRefFields{$selfRefField};
6a0f3000 467 }
468 # Check all other reference fields were accounted for
469 return 0 unless keys %otherRefFields == 0;
470
b8d24485 471 return 0 unless $case_insensitive ? uc($self->reference_table) eq uc($other->reference_table) : $self->reference_table eq $other->reference_table;
abf315bb 472 return 0 unless $self->match_type eq $other->match_type;
473 return 0 unless $self->on_delete eq $other->on_delete;
474 return 0 unless $self->on_update eq $other->on_update;
4598b71c 475 return 0 unless $self->_compare_objects(scalar $self->options, scalar $other->options);
476 return 0 unless $self->_compare_objects(scalar $self->extra, scalar $other->extra);
abf315bb 477 return 1;
96a5759e 478};
abf315bb 479
96a5759e 480# Must come after all 'has' declarations
481around new => \&ex2err;
482
3c5de62a 4831;
484
3c5de62a 485=pod
486
487=head1 AUTHOR
488
c3b0b535 489Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
3c5de62a 490
491=cut