49
|
1 use strict;
|
|
2 package IMPL::SQL::Schema::Table;
|
|
3
|
232
|
4 use IMPL::lang qw(:declare is);
|
49
|
5
|
165
|
6 use parent qw(
|
194
|
7 IMPL::Object
|
|
8 IMPL::Object::Disposable
|
163
|
9 );
|
165
|
10
|
167
|
11 require IMPL::SQL::Schema::Column;
|
|
12 require IMPL::SQL::Schema::Constraint;
|
|
13 require IMPL::SQL::Schema::Constraint::PrimaryKey;
|
|
14 require IMPL::SQL::Schema::Constraint::ForeignKey;
|
165
|
15
|
49
|
16 use IMPL::Class::Property::Direct;
|
|
17
|
|
18 BEGIN {
|
167
|
19 public _direct property name => PROP_GET;
|
|
20 public _direct property schema => PROP_GET;
|
|
21 public _direct property columns => PROP_GET;
|
|
22 public _direct property constraints => PROP_GET;
|
|
23 public _direct property columnsByName => 0;
|
|
24 public _direct property primaryKey => PROP_GET;
|
|
25 public _direct property tag => PROP_ALL;
|
49
|
26 }
|
|
27
|
|
28 sub CTOR {
|
|
29 my ($this,%args) = @_;
|
|
30
|
165
|
31 $this->{$name} = $args{'name'} or die new IMPL::InvalidArgumentException('a table name is required');
|
|
32 $this->{$schema} = $args{'schema'} or die new IMPL::InvalidArgumentException('a parent schema is required');
|
|
33
|
|
34 if ($args{columns}) {
|
194
|
35 die new IMPL::InvalidOperationException('A columns property should be a reference to an array') unless ref $args{columns} eq 'ARRAY';
|
|
36
|
|
37 $this->InsertColumn($_) foreach @{$args{columns}};
|
165
|
38 }
|
|
39
|
|
40 if ($args{constraints}) {
|
194
|
41 die new IMPL::InvalidOperationException('A constraints property should be a reference to an array') unless ref $args{constraints} eq 'ARRAY';
|
|
42
|
|
43 $this->AddConstraint($_) foreach @{$args{constraints}};
|
165
|
44 }
|
49
|
45 }
|
|
46
|
|
47 sub InsertColumn {
|
|
48 my ($this,$column,$index) = @_;
|
|
49
|
165
|
50 $index = ($this->{$columns} ? scalar(@{$this->{$columns}}) : 0) if not defined $index;
|
49
|
51
|
165
|
52 die new IMPL::InvalidArgumentException("The index is out of range") if ($index < 0 || $index > ($this->{$columns} ? scalar(@{$this->{$columns}}) : 0));
|
49
|
53
|
|
54 if (UNIVERSAL::isa($column,'IMPL::SQL::Schema::Column')) {
|
|
55
|
|
56 } elsif (UNIVERSAL::isa($column,'HASH')) {
|
|
57 $column = new IMPL::SQL::Schema::Column(%{$column});
|
|
58 } else {
|
|
59 die new IMPL::InvalidArgumentException("The invalid column parameter");
|
|
60 }
|
|
61
|
165
|
62 if (exists $this->{$columnsByName}->{$column->name}) {
|
49
|
63 die new IMPL::InvalidOperationException("The column already exists",$column->name);
|
|
64 } else {
|
165
|
65 $this->{$columnsByName}->{$column->name} = $column;
|
|
66 splice @{$this->{$columns}},$index,0,$column;
|
49
|
67 }
|
|
68
|
|
69 return $column;
|
|
70 }
|
|
71
|
|
72 sub RemoveColumn {
|
|
73 my ($this,$NameOrColumn,$Force) = @_;
|
|
74
|
|
75 my $ColName;
|
|
76 if (UNIVERSAL::isa($NameOrColumn,'IMPL::SQL::Schema::Column')) {
|
165
|
77 $ColName = $NameOrColumn->name;
|
49
|
78 } elsif (not ref $NameOrColumn) {
|
|
79 $ColName = $NameOrColumn;
|
|
80 }
|
|
81
|
165
|
82 if (exists $this->{$columnsByName}->{$ColName}) {
|
49
|
83 my $index = 0;
|
165
|
84 foreach my $column(@{$this->{$columns}}) {
|
|
85 last if $column->name eq $ColName;
|
49
|
86 $index++;
|
|
87 }
|
|
88
|
165
|
89 my $column = $this->{$columns}[$index];
|
49
|
90 if (my @constraints = $this->GetColumnConstraints($column)){
|
|
91 $Force or die new IMPL::InvalidOperationException('Can\'t remove column which is used in the constraints',@constraints);
|
|
92 $this->RemoveConstraint($_) foreach @constraints;
|
|
93 }
|
|
94
|
165
|
95 my $removed = splice @{$this->{$columns}},$index,1;
|
|
96 delete $this->{$columnsByName}->{$ColName};
|
49
|
97 return $removed;
|
|
98 } else {
|
165
|
99 die new IMPL::InvalidOperationException("The column not found",$NameOrColumn->name);
|
49
|
100 }
|
|
101 }
|
|
102
|
167
|
103 sub GetColumn {
|
49
|
104 my ($this,$name) = @_;
|
|
105
|
165
|
106 return $this->{$columnsByName}->{$name};
|
49
|
107 }
|
|
108
|
167
|
109 sub GetColumnAt {
|
49
|
110 my ($this,$index) = @_;
|
|
111
|
165
|
112 die new IMPL::InvalidArgumentException("The index is out of range")
|
194
|
113 if $index < 0 || $index >= ($this->{$columns} ? scalar(@{$this->{$columns}}) : 0);
|
49
|
114
|
165
|
115 return $this->{$columns}[$index];
|
|
116 }
|
|
117
|
269
|
118 sub SetColumnPosition {
|
|
119 my ($this,$nameOrColumn,$pos) = @_;
|
|
120
|
|
121 my $colName;
|
|
122 if (is($nameOrColumn,'IMPL::SQL::Schema::Column')) {
|
|
123 $colName = $nameOrColumn->name;
|
|
124 } elsif (not ref $nameOrColumn) {
|
|
125 $colName = $nameOrColumn;
|
|
126 } else {
|
|
127 die IMPL::InvalidArgumentException->new(column => 'The specified column isn\'t found in the table');
|
|
128 }
|
|
129
|
|
130 die IMPL::InvalidArgumentException->new( 'pos' => 'The specified position is invalid')
|
|
131 if not defined $pos || $pos < 0 || $pos >= $this->columnsCount;
|
|
132
|
|
133 my $index = 0;
|
|
134 foreach my $column(@{$this->{$columns}}) {
|
|
135 last if $column->name eq $colName;
|
|
136 $index++;
|
|
137 }
|
|
138
|
|
139 if ($pos != $index) {
|
|
140 #position needs to be changed;
|
|
141
|
|
142 my ($column) = splice @{$this->{$columns}}, $index, 1;
|
|
143 splice @{$this->{$columns}}, $pos, 0, $column;
|
|
144 }
|
|
145
|
|
146 return;
|
|
147 }
|
|
148
|
|
149 sub columnsCount {
|
|
150 my ($this) = @_;
|
194
|
151
|
|
152 return scalar(@{$this->{$columns}});
|
49
|
153 }
|
|
154
|
269
|
155 sub ColumnsCount {
|
|
156 goto &columnsCount;
|
|
157 }
|
|
158
|
49
|
159 sub AddConstraint {
|
194
|
160 my $this = shift;
|
168
|
161 if (@_ == 1) {
|
194
|
162 my ($Constraint) = @_;
|
|
163
|
|
164 die new IMPL::InvalidArgumentException('The invalid parameter') if not is($Constraint,typeof IMPL::SQL::Schema::Constraint);
|
|
165
|
|
166 $Constraint->table == $this or die new IMPL::InvalidOperationException('The constaint must belong to the target table');
|
|
167
|
|
168 if (exists $this->{$constraints}->{$Constraint->name}) {
|
|
169 die new IMPL::InvalidOperationException('The table already has the specified constraint',$Constraint->name);
|
|
170 } else {
|
|
171 if (UNIVERSAL::isa($Constraint,'IMPL::SQL::Schema::Constraint::PrimaryKey')) {
|
|
172 not $this->{$primaryKey} or die new IMPL::InvalidOperationException('The table already has a primary key');
|
|
173 $this->{$primaryKey} = $Constraint;
|
|
174 }
|
|
175
|
|
176 $this->{$constraints}->{$Constraint->name} = $Constraint;
|
|
177 }
|
168
|
178 } elsif( @_ == 2) {
|
194
|
179 my ($type,$params) = @_;
|
|
180
|
|
181 $type = IMPL::SQL::Schema::Constraint->ResolveAlias($type) or
|
|
182 die new IMPL::Exception("Can't resolve a constraint alias",$_[0]);
|
|
183
|
|
184 $params->{table} = $this;
|
|
185
|
|
186 $this->AddConstraint($type->new(%$params));
|
165
|
187 } else {
|
194
|
188 die new IMPL::Exception("Wrong arguments number",scalar(@_));
|
49
|
189 }
|
|
190 }
|
|
191
|
|
192 sub RemoveConstraint {
|
|
193 my ($this,$Constraint,$Force) = @_;
|
|
194
|
165
|
195 my $cn = UNIVERSAL::isa($Constraint,'IMPL::SQL::Schema::Constraint') ? $Constraint->name : $Constraint;
|
|
196 $Constraint = $this->{$constraints}->{$cn} or die new IMPL::InvalidOperationException('The specified constraint doesn\'t exists',$cn);
|
49
|
197
|
|
198 if (UNIVERSAL::isa($Constraint,'IMPL::SQL::Schema::Constraint::PrimaryKey')) {
|
165
|
199 not scalar keys %{$this->{$primaryKey}->ConnectedFK} or die new IMPL::InvalidOperationException('Can\'t remove Primary Key unless some foreign keys referenses it');
|
49
|
200
|
165
|
201 delete $this->{$primaryKey};
|
49
|
202 }
|
|
203 $Constraint->Dispose;
|
165
|
204 delete $this->{$constraints}->{$cn};
|
49
|
205 return $cn;
|
|
206 }
|
|
207
|
165
|
208 sub GetConstraint {
|
194
|
209 my ($this,$name) = @_;
|
|
210
|
|
211 return $this->{$constraints}{$name};
|
165
|
212 }
|
|
213
|
167
|
214 sub GetConstraints {
|
194
|
215 my ($this) = @_;
|
|
216
|
|
217 return wantarray ? values %{$this->{$constraints}} : [values %{$this->{$constraints}}];
|
167
|
218 }
|
|
219
|
49
|
220 sub GetColumnConstraints {
|
|
221 my ($this,@Columns) = @_;
|
|
222
|
165
|
223 my @cn = map { UNIVERSAL::isa($_ ,'IMPL::SQL::Schema::Column') ? $_ ->name : $_ } @Columns;
|
|
224 exists $this->{$columnsByName}->{$_} or die new IMPL::InvalidOperationException('The specified column isn\'t found',$_) foreach @cn;
|
49
|
225
|
165
|
226 return grep {$_->HasColumn(@cn)} values %{$this->{$constraints}};
|
49
|
227 }
|
|
228
|
|
229 sub SetPrimaryKey {
|
|
230 my ($this,@ColumnList) = @_;
|
|
231
|
165
|
232 $this->AddConstraint(new IMPL::SQL::Schema::Constraint::PrimaryKey(name => $this->{$name}.'_PK', table => $this, columns => \@ColumnList));
|
49
|
233 }
|
|
234
|
|
235 sub LinkTo {
|
|
236 my ($this,$table,@ColumnList) = @_;
|
165
|
237 $table->primaryKey or die new IMPL::InvalidOperationException('The referenced table must have a primary key');
|
|
238 my $constraintName = $this->{$name}.'_'.$table->name.'_FK_'.join('_',map {ref $_ ? $_->name : $_} @ColumnList);
|
168
|
239 $this->AddConstraint(new IMPL::SQL::Schema::Constraint::ForeignKey(name => $constraintName, table => $this, columns => \@ColumnList, referencedTable => $table, referencedColumns => $table->primaryKey->columns->as_list));
|
49
|
240 }
|
|
241
|
|
242 sub Dispose {
|
|
243 my ($this) = @_;
|
|
244
|
165
|
245 $_->Dispose() foreach values %{$this->{$constraints}};
|
49
|
246
|
|
247 undef %{$this};
|
|
248 $this->SUPER::Dispose();
|
|
249 }
|
|
250
|
167
|
251 sub SameValue {
|
194
|
252 my ($this,$other) = @_;
|
|
253
|
|
254 return 0 unless is $other, typeof $this;
|
|
255
|
|
256 return 0 unless $this->name eq $other->name;
|
|
257 return 0 unless $this->ColumnsCount eq $other->ColumnsCount;
|
|
258
|
|
259 for (my $i = 0; $i < $this->ColumsCount; $i ++) {
|
|
260 return 0 unless $this->($i)->SameValue($other->GetColumnAt($i));
|
|
261 }
|
|
262
|
|
263 my %thisConstraints = map { $_->name, $_ } $this->GetConstraints();
|
|
264 my %otherConstraints = map { $_->name, $_ } $other->GetConstraints();
|
|
265
|
|
266 foreach my $name ( keys %thisConstraints ) {
|
|
267 return 0 unless $otherConstraints{$name};
|
|
268 return 0 unless $thisConstraints{$name}->SameValue(delete $otherConstraints{$name});
|
|
269 }
|
|
270
|
|
271 return 0 if %otherConstraints;
|
|
272
|
|
273 return 1;
|
167
|
274 }
|
|
275
|
49
|
276 1;
|
165
|
277
|
|
278
|