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