File Coverage

blib/lib/SQL/Translator/Schema/Constraint.pm
Criterion Covered Total %
statement 52 53 98.1
branch 39 48 81.2
condition n/a
subroutine 9 9 100.0
pod 2 2 100.0
total 102 112 91.0


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