Mercurial > pub > Impl
comparison Lib/IMPL/SQL/Schema/Traits.pm @ 163:6ce1f052b90a
temp commit
author | wizard |
---|---|
date | Tue, 15 Mar 2011 02:32:42 +0300 |
parents | 16ada169ca75 |
children | eb3e9861a761 |
comparison
equal
deleted
inserted
replaced
162:39c8788eded5 | 163:6ce1f052b90a |
---|---|
1 package IMPL::SQL::Schema::Traits; | 1 package IMPL::SQL::Traits; |
2 use strict; | 2 use strict; |
3 use IMPL::_core::version; | |
4 use IMPL::Exception(); | |
5 | |
3 use base qw(IMPL::Object IMPL::Object::Autofill); | 6 use base qw(IMPL::Object IMPL::Object::Autofill); |
7 | |
8 # this is a base class for all table traits | |
9 package IMPL::SQL::Traits::Table; | |
10 | |
11 our @ISA = qw(IMPL::SQL::Traits); | |
12 | |
4 use IMPL::Class::Property; | 13 use IMPL::Class::Property; |
5 use IMPL::Class::Property::Direct; | |
6 | |
7 use constant { | |
8 STATE_NORMAL => 0, | |
9 STATE_UPDATED => 1, | |
10 STATE_CREATED => 2, | |
11 STATE_REMOVED => 3, | |
12 STATE_PENDING => 4 | |
13 } ; | |
14 | 14 |
15 BEGIN { | 15 BEGIN { |
16 public _direct property SrcSchema => prop_all; | 16 public property tableName => prop_all; |
17 public _direct property DstSchema => prop_all; | |
18 public _direct property PendingActions => prop_get; | |
19 public _direct property TableInfo => prop_get; | |
20 public _direct property Handler => prop_get; | |
21 public _direct property TableMap => prop_none; | |
22 public _direct property KeepTables => prop_all; | |
23 } | 17 } |
24 | 18 |
25 __PACKAGE__->PassThroughArgs; | 19 sub verify { |
26 | 20 my ($this, $schema) = @_; |
27 sub CTOR { | |
28 my $this = shift; | |
29 | |
30 $this->{$SrcSchema} or die new IMPL::InvalidArgumentException('A source schema is required'); | |
31 $this->{$DstSchema} or die new IMPL::InvalidArgumentException('A destination schema is required'); | |
32 $this->{$Handler} or die new IMPL::InvalidArgumentException('A handler is required to produce the update batch'); | |
33 | |
34 $this->{$TableInfo} = {}; | |
35 $this->{$PendingActions} = []; | |
36 | |
37 } | 21 } |
38 | 22 |
39 sub UpdateTable { | 23 package IMPL::SQL::Traits::Table::Create; |
40 my ($this,$srcTable) = @_; | |
41 | |
42 return 1 if $this->{$TableInfo}->{$srcTable->Name}->{'processed'}; | |
43 | |
44 my $dstTableName = $this->{$TableMap}->{$srcTable->Name} ? $this->{$TableMap}->{$srcTable->Name} : $srcTable->Name; | |
45 my $dstTable = $this->{$DstSchema}->Tables->{$dstTableName}; | |
46 | |
47 $this->{$TableInfo}->{$srcTable->Name}->{'processed'} = 1; | |
48 | |
49 if (not $dstTable) { | |
50 $this->DropTable($srcTable) if not $this->{$KeepTables}; | |
51 return 1; | |
52 } | |
53 | |
54 if ( not grep {$srcTable->Column($_->Name)} @{$dstTable->Columns} ) { | |
55 | |
56 $this->{$TableInfo}->{$srcTable->Name}->{'NewName'} = $dstTable->Name if $srcTable->Name ne $dstTable->Name; | |
57 | |
58 $this->DropTable($srcTable); | |
59 $this->CreateTable($dstTable); | |
60 | |
61 return 1; | |
62 } | |
63 | |
64 if ($srcTable->Name ne $dstTableName) { | |
65 $this->RenameTable($srcTable,$dstTableName); | |
66 } | |
67 | |
68 my %dstConstraints = %{$dstTable->Constraints}; | |
69 | |
70 foreach my $srcConstraint (values %{$srcTable->Constraints}) { | |
71 if (my $dstConstraint = delete $dstConstraints{$srcConstraint->Name}) { | |
72 $this->UpdateConstraint($srcConstraint,$dstConstraint); | |
73 } else { | |
74 $this->DropConstraint($srcConstraint); | |
75 } | |
76 } | |
77 | |
78 my $i = 0; | |
79 my %dstColumns = map { $_->Name, $i++} @{$dstTable->Columns} ; | |
80 | |
81 # сначала удаляем столбцы | |
82 # потом добавляем недостающие и изменяем столбцы в нужном порядке | |
83 | |
84 my @columnsToUpdate; | |
85 | |
86 foreach my $srcColumn (@{$srcTable->Columns}) { | |
87 if (defined (my $dstColumnIndex = delete $dstColumns{$srcColumn->Name})) { | |
88 push @columnsToUpdate, { Action => 'update', ColumnSrc => $srcColumn, ColumnDst => $dstTable->ColumnAt($dstColumnIndex), NewPosition => $dstColumnIndex}; | |
89 } else { | |
90 $this->DropColumn($srcTable,$srcColumn); | |
91 } | |
92 } | |
93 push @columnsToUpdate, map { {Action => 'add', ColumnDst => $dstTable->ColumnAt($_), NewPosition => $_} } values %dstColumns; | |
94 | |
95 foreach my $action (sort {$a->{'NewPosition'} <=> $b->{'NewPosition'}} @columnsToUpdate ) { | |
96 if ($action->{'Action'} eq 'update') { | |
97 $this->UpdateColumn($srcTable,@$action{'ColumnSrc','ColumnDst'},$dstTable,$action->{'NewPosition'}); # change type and position | |
98 }elsif ($action->{'Action'} eq 'add') { | |
99 $this->AddColumn($srcTable,$action->{'ColumnDst'},$dstTable,$action->{'NewPosition'}); # add at specified position | |
100 } | |
101 } | |
102 | |
103 foreach my $dstConstraint (values %dstConstraints) { | |
104 $this->AddConstraint($dstConstraint); | |
105 } | |
106 | |
107 $this->{$TableInfo}{$srcTable->Name}{'State'} = STATE_UPDATED; | |
108 } | |
109 | 24 |
110 sub UpdateConstraint { | 25 our @ISA = qw(IMPL::SQL::Traits::Table); |
111 my ($this,$src,$dst) = @_; | |
112 | |
113 if (not ConstraintEquals($src,$dst)) { | |
114 if (UNIVERSAL::isa($src,'IMPL::SQL::Schema::Constraint::PrimaryKey')) { | |
115 $this->UpdateTable($_->Table) foreach values %{$src->ConnectedFK}; | |
116 } | |
117 $this->DropConstraint($src); | |
118 $this->AddConstraint($dst); | |
119 } else { | |
120 $this->{$TableInfo}->{$this->MapTableName($src->Table->Name)}->{'Constraints'}->{$src->Name} = STATE_UPDATED; | |
121 } | |
122 } | |
123 | 26 |
124 sub ConstraintEquals { | 27 package IMPL::SQL::Traits::Table::Drop; |
125 my ($src,$dst) = @_; | |
126 | |
127 ref $src eq ref $dst or return 0; | |
128 | |
129 my @dstColumns = @{$dst->Columns}; | |
130 scalar(@{$src->Columns}) == scalar(@{$dst->Columns}) and not grep { my $column = shift @dstColumns; not $column->isSame($_) } @{$src->Columns} or return 0; | |
131 | |
132 not UNIVERSAL::isa($src,'IMPL::SQL::Schema::Constraint::ForeignKey') or ConstraintEquals($src->ReferencedPrimaryKey,$dst->ReferencedPrimaryKey) or return 0; | |
133 | |
134 1; | |
135 } | |
136 | 28 |
137 sub UpdateSchema { | 29 our @ISA = qw(IMPL::SQL::Traits::Table); |
138 my ($this) = @_; | |
139 | |
140 my %Updated = map { $this->UpdateTable($_); $this->MapTableName($_->Name) , 1; } values %{$this->{$SrcSchema}->Tables ? $this->{$SrcSchema}->Tables : {} }; | |
141 | |
142 $this->CreateTable($_) foreach grep {not $Updated{$_->Name}} values %{$this->{$DstSchema}->Tables}; | |
143 | |
144 $this->ProcessPendingActions(); | |
145 } | |
146 | 30 |
147 sub RenameTable { | 31 package IMPL::SQL::Traits::Table::AlterAttributes; |
148 my ($this,$tblSrc,$tblDstName) = @_; | |
149 | |
150 $this->{$Handler}->AlterTableRename($tblSrc->Name,$tblDstName); | |
151 $this->{$TableInfo}->{$tblSrc->Name}->{'NewName'} = $tblDstName; | |
152 } | |
153 | 32 |
154 sub MapTableName { | 33 our @ISA = qw(IMPL::SQL::Traits::Table); |
155 my ($this,$srcName) = @_; | |
156 | |
157 $this->{$TableInfo}->{$srcName}->{'NewName'} ? $this->{$TableInfo}->{$srcName}->{'NewName'} : $srcName; | |
158 } | |
159 | 34 |
160 sub DropTable { | 35 package IMPL::SQL::Traits::Table::AlterName; |
161 my ($this,$tbl) = @_; | |
162 | |
163 if ($tbl->PrimaryKey) { | |
164 $this->UpdateTable($_->Table) foreach values %{$tbl->PrimaryKey->ConnectedFK}; | |
165 } | |
166 | |
167 $this->{$Handler}->DropTable($this->MapTableName($tbl->Name)); | |
168 $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'State'} = STATE_REMOVED; | |
169 $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Constraints'} = {map {$_,STATE_REMOVED} keys %{$tbl->Constraints}}; | |
170 $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Columns'} = {map { $_->Name, STATE_REMOVED} @{$tbl->Columns}}; | |
171 | |
172 return 1; | |
173 } | |
174 | 36 |
175 sub CreateTable { | 37 our @ISA = qw(IMPL::SQL::Traits::Table); |
176 my ($this,$tbl) = @_; | |
177 | |
178 # создаем таблицу, кроме внешних ключей | |
179 $this->{$Handler}->CreateTable($tbl,skip_foreign_keys => 1); | |
180 | |
181 $this->{$TableInfo}->{$tbl->Name}->{'State'} = STATE_CREATED; | |
182 | |
183 $this->{$TableInfo}->{$tbl->Name}->{'Columns'} = {map { $_->Name, STATE_CREATED } @{$tbl->Columns}}; | |
184 $this->{$TableInfo}->{$tbl->Name}->{'Constraints'} = {map {$_->Name, STATE_CREATED} grep { not UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey') } values %{$tbl->Constraints}}; | |
185 | |
186 $this->AddConstraint($_) foreach grep { UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey') } values %{$tbl->Constraints}; | |
187 | |
188 return 1; | |
189 } | |
190 | 38 |
191 sub AddColumn { | |
192 my ($this,$tblSrc,$column,$tblDst,$pos) = @_; | |
193 | |
194 $this->{$Handler}->AlterTableAddColumn($this->MapTableName($tblSrc->Name),$column,$tblDst,$pos); | |
195 $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_CREATED; | |
196 | |
197 return 1; | |
198 } | |
199 | 39 |
200 sub DropColumn { | |
201 my ($this,$tblSrc,$column) = @_; | |
202 $this->{$Handler}->AlterTableDropColumn($this->MapTableName($tblSrc->Name),$column->Name); | |
203 $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_REMOVED; | |
204 | |
205 return 1; | |
206 } | |
207 | 40 |
208 sub UpdateColumn { | 41 package IMPL::SQL::Traits::Column; |
209 my ($this,$tblSrc,$srcColumn,$dstColumn,$tblDst,$pos) = @_; | |
210 | |
211 if ($srcColumn->isSame($dstColumn) and $pos < @{$tblSrc->Columns} and $tblSrc->ColumnAt($pos) == $srcColumn) { | |
212 $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; | |
213 return 1; | |
214 } | |
215 | |
216 $this->{$Handler}->AlterTableChangeColumn($this->MapTableName($tblSrc->Name),$dstColumn,$tblDst,$pos); | |
217 $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; | |
218 | |
219 return 1; | |
220 } | |
221 | 42 |
222 sub DropConstraint { | 43 our @ISA = qw(SQL::IMPL::Traits); |
223 my ($this,$constraint) = @_; | |
224 | |
225 $this->{$Handler}->AlterTableDropConstraint($this->MapTableName($constraint->Table->Name),$constraint); | |
226 $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_REMOVED; | |
227 | |
228 return 1; | |
229 } | |
230 | 44 |
231 sub IfUndef { | 45 package IMPL::SQL::Traits::Column::Create; |
232 my ($value,$default) = @_; | |
233 | |
234 return defined $value ? $value : $default; | |
235 } | |
236 | 46 |
237 sub AddConstraint { | 47 our @ISA = qw(IMPL::SQL::Traits::Column); |
238 my ($this,$constraint) = @_; | |
239 | |
240 # перед добавлением ограничения нужно убедиться в том, что созданы все необходимые столбцы и сопутствующие | |
241 # ограничения (например первичные ключи) | |
242 | |
243 my $pending; | |
244 | |
245 $pending = grep { | |
246 my $column = $_; | |
247 not grep { | |
248 IfUndef($this->{$TableInfo}{$constraint->Table->Name}{'Columns'}{$column->Name}, STATE_NORMAL) == $_ | |
249 } (STATE_UPDATED, STATE_CREATED) | |
250 } @{$constraint->Columns}; | |
251 | |
252 if ($pending) { | |
253 push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; | |
254 return 2; | |
255 } else { | |
256 if (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::ForeignKey')) { | |
257 if (not grep { IfUndef($this->{$TableInfo}{$constraint->ReferencedPrimaryKey->Table->Name}{'Constraints'}{$constraint->ReferencedPrimaryKey->Name},STATE_NORMAL) == $_} (STATE_UPDATED, STATE_CREATED)) { | |
258 push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; | |
259 return 2; | |
260 } | |
261 } | |
262 $this->{$Handler}->AlterTableAddConstraint($constraint->Table->Name,$constraint); | |
263 $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_CREATED; | |
264 } | |
265 } | |
266 | 48 |
267 sub ProcessPendingActions { | 49 package IMPL::SQL::Traits::Column::Drop; |
268 my ($this) = @_; | 50 |
269 | 51 our @ISA = qw(IMPL::SQL::Traits::Column); |
270 while (my $action = shift @{$this->{$PendingActions}}) { | 52 |
271 $action->{'Action'}->($this,@{$action->{'Args'}}); | 53 package IMPL::SQL::Traits::Column::Alter; |
272 } | 54 |
273 } | 55 our @ISA = qw(IMPL::SQL::Traits::Column); |
56 | |
274 | 57 |
275 1; | 58 1; |
59 | |
60 __END__ | |
61 | |
62 =pod | |
63 | |
64 =head1 NAME | |
65 | |
66 C<IMPL::SQL::Traits> - Операции над объектками SQL схемы. | |
67 | |
68 =head1 DESCRIPTION | |
69 | |
70 Изменения схемы могу быть представлены в виде последовательности примитивных операций. | |
71 | |
72 | |
73 =cut |