Mercurial > pub > Impl
comparison Lib/Schema/DB/Traits.pm @ 0:03e58a454b20
Создан репозитарий
author | Sergey |
---|---|
date | Tue, 14 Jul 2009 12:54:37 +0400 |
parents | |
children | 16ada169ca75 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:03e58a454b20 |
---|---|
1 package Schema::DB::Traits; | |
2 use strict; | |
3 use Common; | |
4 our @ISA = qw (Object); | |
5 | |
6 use constant { | |
7 STATE_NORMAL => 0, | |
8 STATE_UPDATED => 1, | |
9 STATE_CREATED => 2, | |
10 STATE_REMOVED => 3, | |
11 STATE_PENDING => 4 | |
12 } ; | |
13 | |
14 BEGIN { | |
15 DeclareProperty SrcSchema => ACCESS_NONE; | |
16 DeclareProperty DstSchema => ACCESS_NONE; | |
17 DeclareProperty PendingActions => ACCESS_READ; | |
18 DeclareProperty TableInfo => ACCESS_READ; | |
19 DeclareProperty Handler => ACCESS_READ; | |
20 DeclareProperty TableMap => ACCESS_NONE; | |
21 DeclareProperty KeepTables => ACCESS_ALL; | |
22 } | |
23 | |
24 sub CTOR { | |
25 my $this = shift; | |
26 $this->SUPER::CTOR(@_); | |
27 | |
28 $this->{$SrcSchema} or die new Exception('A source schema is required'); | |
29 $this->{$DstSchema} or die new Exception('A destination schema is required'); | |
30 $this->{$Handler} or die new Exception('A handler is required to produce the update batch'); | |
31 | |
32 $this->{$TableInfo} = {}; | |
33 $this->{$PendingActions} = []; | |
34 | |
35 } | |
36 | |
37 sub UpdateTable { | |
38 my ($this,$srcTable) = @_; | |
39 | |
40 return 1 if $this->{$TableInfo}->{$srcTable->Name}->{'processed'}; | |
41 | |
42 my $dstTableName = $this->{$TableMap}->{$srcTable->Name} ? $this->{$TableMap}->{$srcTable->Name} : $srcTable->Name; | |
43 my $dstTable = $this->{$DstSchema}->Tables->{$dstTableName}; | |
44 | |
45 $this->{$TableInfo}->{$srcTable->Name}->{'processed'} = 1; | |
46 | |
47 if (not $dstTable) { | |
48 $this->DropTable($srcTable) if not $this->{$KeepTables}; | |
49 return 1; | |
50 } | |
51 | |
52 if ( not grep {$srcTable->Column($_->Name)} $dstTable->Columns ) { | |
53 | |
54 $this->{$TableInfo}->{$srcTable->Name}->{'NewName'} = $dstTable->Name if $srcTable->Name ne $dstTable->Name; | |
55 | |
56 $this->DropTable($srcTable); | |
57 $this->CreateTable($dstTable); | |
58 | |
59 return 1; | |
60 } | |
61 | |
62 if ($srcTable->Name ne $dstTableName) { | |
63 $this->RenameTable($srcTable,$dstTableName); | |
64 } | |
65 | |
66 my %dstConstraints = %{$dstTable->Constraints}; | |
67 | |
68 foreach my $srcConstraint (values %{$srcTable->Constraints}) { | |
69 if (my $dstConstraint = delete $dstConstraints{$srcConstraint->Name}) { | |
70 $this->UpdateConstraint($srcConstraint,$dstConstraint); | |
71 } else { | |
72 $this->DropConstraint($srcConstraint); | |
73 } | |
74 } | |
75 | |
76 my $i = 0; | |
77 my %dstColumns = map { $_->Name, $i++} $dstTable->Columns ; | |
78 | |
79 # ñíà÷àëà óäàëÿåì ñòîëáöû | |
80 # ïîòîì äîáàâëÿåì íåäîñòàþùèå è èçìåíÿåì ñòîëáöû â íóæíîì ïîðÿäêå | |
81 | |
82 my @columnsToUpdate; | |
83 | |
84 foreach my $srcColumn ($srcTable->Columns) { | |
85 if (defined (my $dstColumnIndex = delete $dstColumns{$srcColumn->Name})) { | |
86 push @columnsToUpdate, { Action => 'update', ColumnSrc => $srcColumn, ColumnDst => $dstTable->ColumnAt($dstColumnIndex), NewPosition => $dstColumnIndex}; | |
87 } else { | |
88 $this->DropColumn($srcTable,$srcColumn); | |
89 } | |
90 } | |
91 push @columnsToUpdate, map { {Action => 'add', ColumnDst => $dstTable->ColumnAt($_), NewPosition => $_} } values %dstColumns; | |
92 | |
93 foreach my $action (sort {$a->{'NewPosition'} <=> $b->{'NewPosition'}} @columnsToUpdate ) { | |
94 if ($action->{'Action'} eq 'update') { | |
95 $this->UpdateColumn($srcTable,@$action{'ColumnSrc','ColumnDst'},$dstTable,$action->{'NewPosition'}); # change type and position | |
96 }elsif ($action->{'Action'} eq 'add') { | |
97 $this->AddColumn($srcTable,$action->{'ColumnDst'},$dstTable,$action->{'NewPosition'}); # add at specified position | |
98 } | |
99 } | |
100 | |
101 foreach my $dstConstraint (values %dstConstraints) { | |
102 $this->AddConstraint($dstConstraint); | |
103 } | |
104 | |
105 $this->{$TableInfo}{$srcTable->Name}{'State'} = STATE_UPDATED; | |
106 } | |
107 | |
108 sub UpdateConstraint { | |
109 my ($this,$src,$dst) = @_; | |
110 | |
111 if (not ConstraintEquals($src,$dst)) { | |
112 if (UNIVERSAL::isa($src,'Schema::DB::Constraint::PrimaryKey')) { | |
113 $this->UpdateTable($_->Table) foreach values %{$src->ConnectedFK}; | |
114 } | |
115 $this->DropConstraint($src); | |
116 $this->AddConstraint($dst); | |
117 } else { | |
118 $this->{$TableInfo}->{$this->MapTableName($src->Table->Name)}->{'Constraints'}->{$src->Name} = STATE_UPDATED; | |
119 } | |
120 } | |
121 | |
122 sub ConstraintEquals { | |
123 my ($src,$dst) = @_; | |
124 | |
125 ref $src eq ref $dst or return 0; | |
126 | |
127 my @dstColumns = $dst->Columns; | |
128 scalar(@{$src->Columns}) == scalar(@{$dst->Columns}) and not grep { my $column = shift @dstColumns; not $column->isSame($_) } $src->Columns or return 0; | |
129 | |
130 not UNIVERSAL::isa($src,'Schema::DB::Constraint::ForeignKey') or ConstraintEquals($src->ReferencedPrimaryKey,$dst->ReferencedPrimaryKey) or return 0; | |
131 | |
132 1; | |
133 } | |
134 | |
135 sub UpdateSchema { | |
136 my ($this) = @_; | |
137 | |
138 my %Updated = map { $this->UpdateTable($_); $this->MapTableName($_->Name) , 1; } values %{$this->{$SrcSchema}->Tables ? $this->{$SrcSchema}->Tables : {} }; | |
139 | |
140 $this->CreateTable($_) foreach grep {not $Updated{$_->Name}} values %{$this->{$DstSchema}->Tables}; | |
141 | |
142 $this->ProcessPendingActions(); | |
143 } | |
144 | |
145 sub RenameTable { | |
146 my ($this,$tblSrc,$tblDstName) = @_; | |
147 | |
148 $this->{$Handler}->AlterTableRename($tblSrc->Name,$tblDstName); | |
149 $this->{$TableInfo}->{$tblSrc->Name}->{'NewName'} = $tblDstName; | |
150 } | |
151 | |
152 sub MapTableName { | |
153 my ($this,$srcName) = @_; | |
154 | |
155 $this->{$TableInfo}->{$srcName}->{'NewName'} ? $this->{$TableInfo}->{$srcName}->{'NewName'} : $srcName; | |
156 } | |
157 | |
158 sub DropTable { | |
159 my ($this,$tbl) = @_; | |
160 | |
161 if ($tbl->PrimaryKey) { | |
162 $this->UpdateTable($_->Table) foreach values %{$tbl->PrimaryKey->ConnectedFK}; | |
163 } | |
164 | |
165 $this->{$Handler}->DropTable($this->MapTableName($tbl->Name)); | |
166 $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'State'} = STATE_REMOVED; | |
167 $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Constraints'} = {map {$_,STATE_REMOVED} keys %{$tbl->Constraints}}; | |
168 $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Columns'} = {map { $_->Name, STATE_REMOVED} $tbl->Columns}; | |
169 | |
170 return 1; | |
171 } | |
172 | |
173 sub CreateTable { | |
174 my ($this,$tbl) = @_; | |
175 | |
176 # ñîçäàåì òàáëèöó, êðîìå âíåøíèõ êëþ÷åé | |
177 $this->{$Handler}->CreateTable($tbl,skip_foreign_keys => 1); | |
178 | |
179 $this->{$TableInfo}->{$tbl->Name}->{'State'} = STATE_CREATED; | |
180 | |
181 $this->{$TableInfo}->{$tbl->Name}->{'Columns'} = {map { $_->Name, STATE_CREATED } $tbl->Columns}; | |
182 $this->{$TableInfo}->{$tbl->Name}->{'Constraints'} = {map {$_->Name, STATE_CREATED} grep { not UNIVERSAL::isa($_,'Schema::DB::Constraint::ForeignKey') } values %{$tbl->Constraints}}; | |
183 | |
184 $this->AddConstraint($_) foreach grep { UNIVERSAL::isa($_,'Schema::DB::Constraint::ForeignKey') } values %{$tbl->Constraints}; | |
185 | |
186 return 1; | |
187 } | |
188 | |
189 sub AddColumn { | |
190 my ($this,$tblSrc,$column,$tblDst,$pos) = @_; | |
191 | |
192 $this->{$Handler}->AlterTableAddColumn($this->MapTableName($tblSrc->Name),$column,$tblDst,$pos); | |
193 $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_CREATED; | |
194 | |
195 return 1; | |
196 } | |
197 | |
198 sub DropColumn { | |
199 my ($this,$tblSrc,$column) = @_; | |
200 $this->{$Handler}->AlterTableDropColumn($this->MapTableName($tblSrc->Name),$column->Name); | |
201 $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_REMOVED; | |
202 | |
203 return 1; | |
204 } | |
205 | |
206 sub UpdateColumn { | |
207 my ($this,$tblSrc,$srcColumn,$dstColumn,$tblDst,$pos) = @_; | |
208 | |
209 if ($srcColumn->isSame($dstColumn) and $pos < @{$tblSrc->Columns} and $tblSrc->ColumnAt($pos) == $srcColumn) { | |
210 $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; | |
211 return 1; | |
212 } | |
213 | |
214 $this->{$Handler}->AlterTableChangeColumn($this->MapTableName($tblSrc->Name),$dstColumn,$tblDst,$pos); | |
215 $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; | |
216 | |
217 return 1; | |
218 } | |
219 | |
220 sub DropConstraint { | |
221 my ($this,$constraint) = @_; | |
222 | |
223 $this->{$Handler}->AlterTableDropConstraint($this->MapTableName($constraint->Table->Name),$constraint); | |
224 $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_REMOVED; | |
225 | |
226 return 1; | |
227 } | |
228 | |
229 sub IfUndef { | |
230 my ($value,$default) = @_; | |
231 | |
232 return defined $value ? $value : $default; | |
233 } | |
234 | |
235 sub AddConstraint { | |
236 my ($this,$constraint) = @_; | |
237 | |
238 # ïåðåä äîáàâëåíèåì îãðàíè÷åíèÿ íóæíî óáåäèòüñÿ â òîì, ÷òî ñîçäàíû âñå íåîáõîäèìûå ñòîëáöû è ñîïóòñòâóþùèå | |
239 # îãðàíè÷åíèÿ (íàïðèìåð ïåðâè÷íûå êëþ÷è) | |
240 | |
241 my $pending; | |
242 | |
243 $pending = grep { my $column = $_; not grep { IfUndef($this->{$TableInfo}{$constraint->Table->Name}{'Columns'}{$column->Name}, STATE_NORMAL) == $_ } (STATE_UPDATED, STATE_CREATED) } $constraint->Columns; | |
244 | |
245 if ($pending) { | |
246 push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; | |
247 return 2; | |
248 } else { | |
249 if (UNIVERSAL::isa($constraint,'Schema::DB::Constraint::ForeignKey')) { | |
250 if (not grep { IfUndef($this->{$TableInfo}{$constraint->ReferencedPrimaryKey->Table->Name}{'Constraints'}{$constraint->ReferencedPrimaryKey->Name},STATE_NORMAL) == $_} (STATE_UPDATED, STATE_CREATED)) { | |
251 push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; | |
252 return 2; | |
253 } | |
254 } | |
255 $this->{$Handler}->AlterTableAddConstraint($constraint->Table->Name,$constraint); | |
256 $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_CREATED; | |
257 } | |
258 } | |
259 | |
260 sub ProcessPendingActions { | |
261 my ($this) = @_; | |
262 | |
263 while (my $action = shift @{$this->{$PendingActions}}) { | |
264 $action->{'Action'}->($this,@{$action->{'Args'}}); | |
265 } | |
266 } | |
267 | |
268 1; |