Filter undef from all constructor args
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Schema / Index.pm
1 package SQL::Translator::Schema::Index;
2
3 =pod
4
5 =head1 NAME
6
7 SQL::Translator::Schema::Index - SQL::Translator index object
8
9 =head1 SYNOPSIS
10
11   use SQL::Translator::Schema::Index;
12   my $index = SQL::Translator::Schema::Index->new(
13       name   => 'foo',
14       fields => [ id ],
15       type   => 'unique',
16   );
17
18 =head1 DESCRIPTION
19
20 C<SQL::Translator::Schema::Index> is the index object.
21
22 Primary and unique keys are table constraints, not indices.
23
24 =head1 METHODS
25
26 =cut
27
28 use Moo;
29 use SQL::Translator::Schema::Constants;
30 use SQL::Translator::Utils qw(parse_list_arg ex2err throw);
31 use SQL::Translator::Types qw(schema_obj);
32 use List::MoreUtils qw(uniq);
33
34 with qw(
35   SQL::Translator::Schema::Role::BuildArgs
36   SQL::Translator::Schema::Role::Extra
37   SQL::Translator::Schema::Role::Error
38   SQL::Translator::Schema::Role::Compare
39 );
40
41 our ( $TABLE_COUNT, $VIEW_COUNT );
42
43 our $VERSION = '1.59';
44
45 my %VALID_INDEX_TYPE = (
46   UNIQUE         => 1,
47   NORMAL         => 1,
48   FULLTEXT       => 1, # MySQL only (?)
49   FULL_TEXT      => 1, # MySQL only (?)
50   SPATIAL        => 1, # MySQL only (?)
51 );
52
53 =head2 new
54
55 Object constructor.
56
57   my $schema = SQL::Translator::Schema::Index->new;
58
59 =head2 fields
60
61 Gets and set the fields the index is on.  Accepts a string, list or
62 arrayref; returns an array or array reference.  Will unique the field
63 names and keep them in order by the first occurrence of a field name.
64
65   $index->fields('id');
66   $index->fields('id', 'name');
67   $index->fields( 'id, name' );
68   $index->fields( [ 'id', 'name' ] );
69   $index->fields( qw[ id name ] );
70
71   my @fields = $index->fields;
72
73 =cut
74
75 has fields => (
76     is => 'rw',
77     default => sub { [] },
78     coerce => sub { [uniq @{parse_list_arg($_[0])}] },
79 );
80
81 around fields => sub {
82     my $orig   = shift;
83     my $self   = shift;
84     my $fields = parse_list_arg( @_ );
85     $self->$orig($fields) if @$fields;
86
87     return wantarray ? @{ $self->$orig } : $self->$orig;
88 };
89
90 sub is_valid {
91
92 =pod
93
94 =head2 is_valid
95
96 Determine whether the index is valid or not.
97
98   my $ok = $index->is_valid;
99
100 =cut
101
102     my $self   = shift;
103     my $table  = $self->table  or return $self->error('No table');
104     my @fields = $self->fields or return $self->error('No fields');
105
106     for my $field ( @fields ) {
107         return $self->error(
108             "Field '$field' does not exist in table '", $table->name, "'"
109         ) unless $table->get_field( $field );
110     }
111
112     return 1;
113 }
114
115 =head2 name
116
117 Get or set the index's name.
118
119   my $name = $index->name('foo');
120
121 =cut
122
123 has name => ( is => 'rw', coerce => sub { defined $_[0] ? $_[0] : '' }, default => sub { '' } );
124
125 =head2 options
126
127 Get or set the index's options (e.g., "using" or "where" for PG).  Returns
128 an array or array reference.
129
130   my @options = $index->options;
131
132 =cut
133
134 has options => (
135     is => 'rw',
136     default => sub { [] },
137     coerce => \&parse_list_arg,
138 );
139
140 around options => sub {
141     my $orig    = shift;
142     my $self    = shift;
143     my $options = parse_list_arg( @_ );
144
145     push @{ $self->$orig }, @$options;
146
147     return wantarray ? @{ $self->$orig } : $self->$orig;
148 };
149
150 =head2 table
151
152 Get or set the index's table object.
153
154   my $table = $index->table;
155
156 =cut
157
158 has table => ( is => 'rw', isa => schema_obj('Table') );
159
160 around table => \&ex2err;
161
162 =head2 type
163
164 Get or set the index's type.
165
166   my $type = $index->type('unique');
167
168 Get or set the index's type.
169
170 Currently there are only four acceptable types: UNIQUE, NORMAL, FULL_TEXT,
171 and SPATIAL. The latter two might be MySQL-specific. While both lowercase
172 and uppercase types are acceptable input, this method returns the type in
173 uppercase.
174
175 =cut
176
177 has type => (
178     is => 'rw',
179     isa => sub {
180         my $type = uc $_[0] or return;
181         throw("Invalid index type: $type") unless $VALID_INDEX_TYPE{$type};
182     },
183     coerce => sub { uc $_[0] },
184     default => sub { 'NORMAL' },
185 );
186
187 around type => \&ex2err;
188
189 =head2 equals
190
191 Determines if this index is the same as another
192
193   my $isIdentical = $index1->equals( $index2 );
194
195 =cut
196
197 around equals => sub {
198     my $orig = shift;
199     my $self = shift;
200     my $other = shift;
201     my $case_insensitive = shift;
202     my $ignore_index_names = shift;
203
204     return 0 unless $self->$orig($other);
205
206     unless ($ignore_index_names) {
207       unless ((!$self->name && ($other->name eq $other->fields->[0])) ||
208         (!$other->name && ($self->name eq $self->fields->[0]))) {
209         return 0 unless $case_insensitive ? uc($self->name) eq uc($other->name) : $self->name eq $other->name;
210       }
211     }
212     #return 0 unless $self->is_valid eq $other->is_valid;
213     return 0 unless $self->type eq $other->type;
214
215     # Check fields, regardless of order
216     my %otherFields = ();  # create a hash of the other fields
217     foreach my $otherField ($other->fields) {
218       $otherField = uc($otherField) if $case_insensitive;
219       $otherFields{$otherField} = 1;
220     }
221     foreach my $selfField ($self->fields) { # check for self fields in hash
222       $selfField = uc($selfField) if $case_insensitive;
223       return 0 unless $otherFields{$selfField};
224       delete $otherFields{$selfField};
225     }
226     # Check all other fields were accounted for
227     return 0 unless keys %otherFields == 0;
228
229     return 0 unless $self->_compare_objects(scalar $self->options, scalar $other->options);
230     return 0 unless $self->_compare_objects(scalar $self->extra, scalar $other->extra);
231     return 1;
232 };
233
234 sub DESTROY {
235     my $self = shift;
236     undef $self->{'table'}; # destroy cyclical reference
237 }
238
239 # Must come after all 'has' declarations
240 around new => \&ex2err;
241
242 1;
243
244 =pod
245
246 =head1 AUTHOR
247
248 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
249
250 =cut