Mercurial > pub > Impl
view Lib/IMPL/SQL/Schema/TraitsOld.pm @ 250:129e48bb5afb
DOM refactoring
ObjectToDOM methods are virtual
QueryToDOM uses inflators
Fixed transform for the complex values in the ObjectToDOM
QueryToDOM doesn't allow to use complex values (HASHes) as values for nodes (overpost problem)
author | sergey |
---|---|
date | Wed, 07 Nov 2012 04:17:53 +0400 |
parents | d1676be8afcc |
children |
line wrap: on
line source
package IMPL::SQL::Schema::Traits; use strict; use parent 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;