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