Mercurial > pub > Impl
diff Lib/IMPL/SQL/Schema/Traits.pm @ 49:16ada169ca75
migrating to the Eclipse IDE
author | wizard@linux-odin.local |
---|---|
date | Fri, 26 Feb 2010 10:49:21 +0300 |
parents | 32d2350fccf9 |
children | 6ce1f052b90a |
line wrap: on
line diff
--- a/Lib/IMPL/SQL/Schema/Traits.pm Fri Feb 26 01:43:42 2010 +0300 +++ b/Lib/IMPL/SQL/Schema/Traits.pm Fri Feb 26 10:49:21 2010 +0300 @@ -1,275 +1,275 @@ -package IMPL::SQL::Schema::Traits; -use strict; -use base qw(IMPL::Object IMPL::Object::Autofill); -use IMPL::Class::Property; -use IMPL::Class::Property::Direct; - -use constant { - STATE_NORMAL => 0, - STATE_UPDATED => 1, - STATE_CREATED => 2, - STATE_REMOVED => 3, - STATE_PENDING => 4 -} ; - -BEGIN { - public _direct property SrcSchema => prop_all; - public _direct property DstSchema => prop_all; - public _direct property PendingActions => prop_get; - public _direct property TableInfo => prop_get; - public _direct property Handler => prop_get; - public _direct property TableMap => prop_none; - public _direct property KeepTables => prop_all; -} - -__PACKAGE__->PassThroughArgs; - -sub CTOR { - my $this = shift; - - $this->{$SrcSchema} or die new IMPL::InvalidArgumentException('A source schema is required'); - $this->{$DstSchema} or die new IMPL::InvalidArgumentException('A destination schema is required'); - $this->{$Handler} or die new IMPL::InvalidArgumentException('A handler is required to produce the update batch'); - - $this->{$TableInfo} = {}; - $this->{$PendingActions} = []; - -} - -sub UpdateTable { - my ($this,$srcTable) = @_; - - return 1 if $this->{$TableInfo}->{$srcTable->Name}->{'processed'}; - - my $dstTableName = $this->{$TableMap}->{$srcTable->Name} ? $this->{$TableMap}->{$srcTable->Name} : $srcTable->Name; - my $dstTable = $this->{$DstSchema}->Tables->{$dstTableName}; - - $this->{$TableInfo}->{$srcTable->Name}->{'processed'} = 1; - - if (not $dstTable) { - $this->DropTable($srcTable) if not $this->{$KeepTables}; - return 1; - } - - if ( not grep {$srcTable->Column($_->Name)} @{$dstTable->Columns} ) { - - $this->{$TableInfo}->{$srcTable->Name}->{'NewName'} = $dstTable->Name if $srcTable->Name ne $dstTable->Name; - - $this->DropTable($srcTable); - $this->CreateTable($dstTable); - - return 1; - } - - if ($srcTable->Name ne $dstTableName) { - $this->RenameTable($srcTable,$dstTableName); - } - - my %dstConstraints = %{$dstTable->Constraints}; - - foreach my $srcConstraint (values %{$srcTable->Constraints}) { - if (my $dstConstraint = delete $dstConstraints{$srcConstraint->Name}) { - $this->UpdateConstraint($srcConstraint,$dstConstraint); - } else { - $this->DropConstraint($srcConstraint); - } - } - - my $i = 0; - my %dstColumns = map { $_->Name, $i++} @{$dstTable->Columns} ; - - # сначала удаляем столбцы - # потом добавляем недостающие и изменяем столбцы в нужном порядке - - my @columnsToUpdate; - - foreach my $srcColumn (@{$srcTable->Columns}) { - if (defined (my $dstColumnIndex = delete $dstColumns{$srcColumn->Name})) { - push @columnsToUpdate, { Action => 'update', ColumnSrc => $srcColumn, ColumnDst => $dstTable->ColumnAt($dstColumnIndex), NewPosition => $dstColumnIndex}; - } else { - $this->DropColumn($srcTable,$srcColumn); - } - } - push @columnsToUpdate, map { {Action => 'add', ColumnDst => $dstTable->ColumnAt($_), NewPosition => $_} } values %dstColumns; - - foreach my $action (sort {$a->{'NewPosition'} <=> $b->{'NewPosition'}} @columnsToUpdate ) { - if ($action->{'Action'} eq 'update') { - $this->UpdateColumn($srcTable,@$action{'ColumnSrc','ColumnDst'},$dstTable,$action->{'NewPosition'}); # change type and position - }elsif ($action->{'Action'} eq 'add') { - $this->AddColumn($srcTable,$action->{'ColumnDst'},$dstTable,$action->{'NewPosition'}); # add at specified position - } - } - - foreach my $dstConstraint (values %dstConstraints) { - $this->AddConstraint($dstConstraint); - } - - $this->{$TableInfo}{$srcTable->Name}{'State'} = STATE_UPDATED; -} - -sub UpdateConstraint { - my ($this,$src,$dst) = @_; - - if (not ConstraintEquals($src,$dst)) { - if (UNIVERSAL::isa($src,'IMPL::SQL::Schema::Constraint::PrimaryKey')) { - $this->UpdateTable($_->Table) foreach values %{$src->ConnectedFK}; - } - $this->DropConstraint($src); - $this->AddConstraint($dst); - } else { - $this->{$TableInfo}->{$this->MapTableName($src->Table->Name)}->{'Constraints'}->{$src->Name} = STATE_UPDATED; - } -} - -sub ConstraintEquals { - my ($src,$dst) = @_; - - ref $src eq ref $dst or return 0; - - my @dstColumns = @{$dst->Columns}; - scalar(@{$src->Columns}) == scalar(@{$dst->Columns}) and not grep { my $column = shift @dstColumns; not $column->isSame($_) } @{$src->Columns} or return 0; - - not UNIVERSAL::isa($src,'IMPL::SQL::Schema::Constraint::ForeignKey') or ConstraintEquals($src->ReferencedPrimaryKey,$dst->ReferencedPrimaryKey) or return 0; - - 1; -} - -sub UpdateSchema { - my ($this) = @_; - - my %Updated = map { $this->UpdateTable($_); $this->MapTableName($_->Name) , 1; } values %{$this->{$SrcSchema}->Tables ? $this->{$SrcSchema}->Tables : {} }; - - $this->CreateTable($_) foreach grep {not $Updated{$_->Name}} values %{$this->{$DstSchema}->Tables}; - - $this->ProcessPendingActions(); -} - -sub RenameTable { - my ($this,$tblSrc,$tblDstName) = @_; - - $this->{$Handler}->AlterTableRename($tblSrc->Name,$tblDstName); - $this->{$TableInfo}->{$tblSrc->Name}->{'NewName'} = $tblDstName; -} - -sub MapTableName { - my ($this,$srcName) = @_; - - $this->{$TableInfo}->{$srcName}->{'NewName'} ? $this->{$TableInfo}->{$srcName}->{'NewName'} : $srcName; -} - -sub DropTable { - my ($this,$tbl) = @_; - - if ($tbl->PrimaryKey) { - $this->UpdateTable($_->Table) foreach values %{$tbl->PrimaryKey->ConnectedFK}; - } - - $this->{$Handler}->DropTable($this->MapTableName($tbl->Name)); - $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'State'} = STATE_REMOVED; - $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Constraints'} = {map {$_,STATE_REMOVED} keys %{$tbl->Constraints}}; - $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Columns'} = {map { $_->Name, STATE_REMOVED} @{$tbl->Columns}}; - - return 1; -} - -sub CreateTable { - my ($this,$tbl) = @_; - - # создаем таблицу, кроме внешних ключей - $this->{$Handler}->CreateTable($tbl,skip_foreign_keys => 1); - - $this->{$TableInfo}->{$tbl->Name}->{'State'} = STATE_CREATED; - - $this->{$TableInfo}->{$tbl->Name}->{'Columns'} = {map { $_->Name, STATE_CREATED } @{$tbl->Columns}}; - $this->{$TableInfo}->{$tbl->Name}->{'Constraints'} = {map {$_->Name, STATE_CREATED} grep { not UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey') } values %{$tbl->Constraints}}; - - $this->AddConstraint($_) foreach grep { UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey') } values %{$tbl->Constraints}; - - return 1; -} - -sub AddColumn { - my ($this,$tblSrc,$column,$tblDst,$pos) = @_; - - $this->{$Handler}->AlterTableAddColumn($this->MapTableName($tblSrc->Name),$column,$tblDst,$pos); - $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_CREATED; - - return 1; -} - -sub DropColumn { - my ($this,$tblSrc,$column) = @_; - $this->{$Handler}->AlterTableDropColumn($this->MapTableName($tblSrc->Name),$column->Name); - $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_REMOVED; - - return 1; -} - -sub UpdateColumn { - my ($this,$tblSrc,$srcColumn,$dstColumn,$tblDst,$pos) = @_; - - if ($srcColumn->isSame($dstColumn) and $pos < @{$tblSrc->Columns} and $tblSrc->ColumnAt($pos) == $srcColumn) { - $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; - return 1; - } - - $this->{$Handler}->AlterTableChangeColumn($this->MapTableName($tblSrc->Name),$dstColumn,$tblDst,$pos); - $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; - - return 1; -} - -sub DropConstraint { - my ($this,$constraint) = @_; - - $this->{$Handler}->AlterTableDropConstraint($this->MapTableName($constraint->Table->Name),$constraint); - $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_REMOVED; - - return 1; -} - -sub IfUndef { - my ($value,$default) = @_; - - return defined $value ? $value : $default; -} - -sub AddConstraint { - my ($this,$constraint) = @_; - - # перед добавлением ограничения нужно убедиться в том, что созданы все необходимые столбцы и сопутствующие - # ограничения (например первичные ключи) - - my $pending; - - $pending = grep { - my $column = $_; - not grep { - IfUndef($this->{$TableInfo}{$constraint->Table->Name}{'Columns'}{$column->Name}, STATE_NORMAL) == $_ - } (STATE_UPDATED, STATE_CREATED) - } @{$constraint->Columns}; - - if ($pending) { - push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; - return 2; - } else { - if (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::ForeignKey')) { - if (not grep { IfUndef($this->{$TableInfo}{$constraint->ReferencedPrimaryKey->Table->Name}{'Constraints'}{$constraint->ReferencedPrimaryKey->Name},STATE_NORMAL) == $_} (STATE_UPDATED, STATE_CREATED)) { - push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; - return 2; - } - } - $this->{$Handler}->AlterTableAddConstraint($constraint->Table->Name,$constraint); - $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_CREATED; - } -} - -sub ProcessPendingActions { - my ($this) = @_; - - while (my $action = shift @{$this->{$PendingActions}}) { - $action->{'Action'}->($this,@{$action->{'Args'}}); - } -} - -1; +package IMPL::SQL::Schema::Traits; +use strict; +use base qw(IMPL::Object IMPL::Object::Autofill); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +use constant { + STATE_NORMAL => 0, + STATE_UPDATED => 1, + STATE_CREATED => 2, + STATE_REMOVED => 3, + STATE_PENDING => 4 +} ; + +BEGIN { + public _direct property SrcSchema => prop_all; + public _direct property DstSchema => prop_all; + public _direct property PendingActions => prop_get; + public _direct property TableInfo => prop_get; + public _direct property Handler => prop_get; + public _direct property TableMap => prop_none; + public _direct property KeepTables => prop_all; +} + +__PACKAGE__->PassThroughArgs; + +sub CTOR { + my $this = shift; + + $this->{$SrcSchema} or die new IMPL::InvalidArgumentException('A source schema is required'); + $this->{$DstSchema} or die new IMPL::InvalidArgumentException('A destination schema is required'); + $this->{$Handler} or die new IMPL::InvalidArgumentException('A handler is required to produce the update batch'); + + $this->{$TableInfo} = {}; + $this->{$PendingActions} = []; + +} + +sub UpdateTable { + my ($this,$srcTable) = @_; + + return 1 if $this->{$TableInfo}->{$srcTable->Name}->{'processed'}; + + my $dstTableName = $this->{$TableMap}->{$srcTable->Name} ? $this->{$TableMap}->{$srcTable->Name} : $srcTable->Name; + my $dstTable = $this->{$DstSchema}->Tables->{$dstTableName}; + + $this->{$TableInfo}->{$srcTable->Name}->{'processed'} = 1; + + if (not $dstTable) { + $this->DropTable($srcTable) if not $this->{$KeepTables}; + return 1; + } + + if ( not grep {$srcTable->Column($_->Name)} @{$dstTable->Columns} ) { + + $this->{$TableInfo}->{$srcTable->Name}->{'NewName'} = $dstTable->Name if $srcTable->Name ne $dstTable->Name; + + $this->DropTable($srcTable); + $this->CreateTable($dstTable); + + return 1; + } + + if ($srcTable->Name ne $dstTableName) { + $this->RenameTable($srcTable,$dstTableName); + } + + my %dstConstraints = %{$dstTable->Constraints}; + + foreach my $srcConstraint (values %{$srcTable->Constraints}) { + if (my $dstConstraint = delete $dstConstraints{$srcConstraint->Name}) { + $this->UpdateConstraint($srcConstraint,$dstConstraint); + } else { + $this->DropConstraint($srcConstraint); + } + } + + my $i = 0; + my %dstColumns = map { $_->Name, $i++} @{$dstTable->Columns} ; + + # сначала удаляем столбцы + # потом добавляем недостающие и изменяем столбцы в нужном порядке + + my @columnsToUpdate; + + foreach my $srcColumn (@{$srcTable->Columns}) { + if (defined (my $dstColumnIndex = delete $dstColumns{$srcColumn->Name})) { + push @columnsToUpdate, { Action => 'update', ColumnSrc => $srcColumn, ColumnDst => $dstTable->ColumnAt($dstColumnIndex), NewPosition => $dstColumnIndex}; + } else { + $this->DropColumn($srcTable,$srcColumn); + } + } + push @columnsToUpdate, map { {Action => 'add', ColumnDst => $dstTable->ColumnAt($_), NewPosition => $_} } values %dstColumns; + + foreach my $action (sort {$a->{'NewPosition'} <=> $b->{'NewPosition'}} @columnsToUpdate ) { + if ($action->{'Action'} eq 'update') { + $this->UpdateColumn($srcTable,@$action{'ColumnSrc','ColumnDst'},$dstTable,$action->{'NewPosition'}); # change type and position + }elsif ($action->{'Action'} eq 'add') { + $this->AddColumn($srcTable,$action->{'ColumnDst'},$dstTable,$action->{'NewPosition'}); # add at specified position + } + } + + foreach my $dstConstraint (values %dstConstraints) { + $this->AddConstraint($dstConstraint); + } + + $this->{$TableInfo}{$srcTable->Name}{'State'} = STATE_UPDATED; +} + +sub UpdateConstraint { + my ($this,$src,$dst) = @_; + + if (not ConstraintEquals($src,$dst)) { + if (UNIVERSAL::isa($src,'IMPL::SQL::Schema::Constraint::PrimaryKey')) { + $this->UpdateTable($_->Table) foreach values %{$src->ConnectedFK}; + } + $this->DropConstraint($src); + $this->AddConstraint($dst); + } else { + $this->{$TableInfo}->{$this->MapTableName($src->Table->Name)}->{'Constraints'}->{$src->Name} = STATE_UPDATED; + } +} + +sub ConstraintEquals { + my ($src,$dst) = @_; + + ref $src eq ref $dst or return 0; + + my @dstColumns = @{$dst->Columns}; + scalar(@{$src->Columns}) == scalar(@{$dst->Columns}) and not grep { my $column = shift @dstColumns; not $column->isSame($_) } @{$src->Columns} or return 0; + + not UNIVERSAL::isa($src,'IMPL::SQL::Schema::Constraint::ForeignKey') or ConstraintEquals($src->ReferencedPrimaryKey,$dst->ReferencedPrimaryKey) or return 0; + + 1; +} + +sub UpdateSchema { + my ($this) = @_; + + my %Updated = map { $this->UpdateTable($_); $this->MapTableName($_->Name) , 1; } values %{$this->{$SrcSchema}->Tables ? $this->{$SrcSchema}->Tables : {} }; + + $this->CreateTable($_) foreach grep {not $Updated{$_->Name}} values %{$this->{$DstSchema}->Tables}; + + $this->ProcessPendingActions(); +} + +sub RenameTable { + my ($this,$tblSrc,$tblDstName) = @_; + + $this->{$Handler}->AlterTableRename($tblSrc->Name,$tblDstName); + $this->{$TableInfo}->{$tblSrc->Name}->{'NewName'} = $tblDstName; +} + +sub MapTableName { + my ($this,$srcName) = @_; + + $this->{$TableInfo}->{$srcName}->{'NewName'} ? $this->{$TableInfo}->{$srcName}->{'NewName'} : $srcName; +} + +sub DropTable { + my ($this,$tbl) = @_; + + if ($tbl->PrimaryKey) { + $this->UpdateTable($_->Table) foreach values %{$tbl->PrimaryKey->ConnectedFK}; + } + + $this->{$Handler}->DropTable($this->MapTableName($tbl->Name)); + $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'State'} = STATE_REMOVED; + $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Constraints'} = {map {$_,STATE_REMOVED} keys %{$tbl->Constraints}}; + $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Columns'} = {map { $_->Name, STATE_REMOVED} @{$tbl->Columns}}; + + return 1; +} + +sub CreateTable { + my ($this,$tbl) = @_; + + # создаем таблицу, кроме внешних ключей + $this->{$Handler}->CreateTable($tbl,skip_foreign_keys => 1); + + $this->{$TableInfo}->{$tbl->Name}->{'State'} = STATE_CREATED; + + $this->{$TableInfo}->{$tbl->Name}->{'Columns'} = {map { $_->Name, STATE_CREATED } @{$tbl->Columns}}; + $this->{$TableInfo}->{$tbl->Name}->{'Constraints'} = {map {$_->Name, STATE_CREATED} grep { not UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey') } values %{$tbl->Constraints}}; + + $this->AddConstraint($_) foreach grep { UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey') } values %{$tbl->Constraints}; + + return 1; +} + +sub AddColumn { + my ($this,$tblSrc,$column,$tblDst,$pos) = @_; + + $this->{$Handler}->AlterTableAddColumn($this->MapTableName($tblSrc->Name),$column,$tblDst,$pos); + $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_CREATED; + + return 1; +} + +sub DropColumn { + my ($this,$tblSrc,$column) = @_; + $this->{$Handler}->AlterTableDropColumn($this->MapTableName($tblSrc->Name),$column->Name); + $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_REMOVED; + + return 1; +} + +sub UpdateColumn { + my ($this,$tblSrc,$srcColumn,$dstColumn,$tblDst,$pos) = @_; + + if ($srcColumn->isSame($dstColumn) and $pos < @{$tblSrc->Columns} and $tblSrc->ColumnAt($pos) == $srcColumn) { + $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; + return 1; + } + + $this->{$Handler}->AlterTableChangeColumn($this->MapTableName($tblSrc->Name),$dstColumn,$tblDst,$pos); + $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; + + return 1; +} + +sub DropConstraint { + my ($this,$constraint) = @_; + + $this->{$Handler}->AlterTableDropConstraint($this->MapTableName($constraint->Table->Name),$constraint); + $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_REMOVED; + + return 1; +} + +sub IfUndef { + my ($value,$default) = @_; + + return defined $value ? $value : $default; +} + +sub AddConstraint { + my ($this,$constraint) = @_; + + # перед добавлением ограничения нужно убедиться в том, что созданы все необходимые столбцы и сопутствующие + # ограничения (например первичные ключи) + + my $pending; + + $pending = grep { + my $column = $_; + not grep { + IfUndef($this->{$TableInfo}{$constraint->Table->Name}{'Columns'}{$column->Name}, STATE_NORMAL) == $_ + } (STATE_UPDATED, STATE_CREATED) + } @{$constraint->Columns}; + + if ($pending) { + push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; + return 2; + } else { + if (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::ForeignKey')) { + if (not grep { IfUndef($this->{$TableInfo}{$constraint->ReferencedPrimaryKey->Table->Name}{'Constraints'}{$constraint->ReferencedPrimaryKey->Name},STATE_NORMAL) == $_} (STATE_UPDATED, STATE_CREATED)) { + push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; + return 2; + } + } + $this->{$Handler}->AlterTableAddConstraint($constraint->Table->Name,$constraint); + $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_CREATED; + } +} + +sub ProcessPendingActions { + my ($this) = @_; + + while (my $action = shift @{$this->{$PendingActions}}) { + $action->{'Action'}->($this,@{$action->{'Args'}}); + } +} + +1;