Mercurial > pub > Impl
comparison lib/IMPL/SQL/Schema/Traits/Diff.pm @ 407:c6e90e02dd17 ref20150831
renamed Lib->lib
| author | cin |
|---|---|
| date | Fri, 04 Sep 2015 19:40:23 +0300 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 406:f23fcb19d3c1 | 407:c6e90e02dd17 |
|---|---|
| 1 package IMPL::SQL::Schema::Traits::Diff; | |
| 2 use strict; | |
| 3 use warnings; | |
| 4 use IMPL::lang qw(:compare :hash is typeof); | |
| 5 | |
| 6 use IMPL::SQL::Schema(); | |
| 7 use IMPL::SQL::Schema::Traits(); | |
| 8 | |
| 9 # defining a constant is a good style to enable compile checks | |
| 10 use constant { | |
| 11 schema_t => 'IMPL::SQL::Schema', | |
| 12 ConstraintForeignKey => 'IMPL::SQL::Schema::Constraint::ForeignKey', | |
| 13 TraitsForeignKey => 'IMPL::SQL::Schema::Traits::ForeignKey', | |
| 14 ConstraintPrimaryKey => 'IMPL::SQL::Schema::Constraint::PrimaryKey', | |
| 15 TraitsPrimaryKey => 'IMPL::SQL::Schema::Traits::PrimaryKey', | |
| 16 ConstraintUnique => 'IMPL::SQL::Schema::Constraint::Unique', | |
| 17 TraitsUnique => 'IMPL::SQL::Schema::Traits::Unique', | |
| 18 ConstraintIndex => 'IMPL::SQL::Schema::Constraint::Index', | |
| 19 TraitsIndex => 'IMPL::SQL::Schema::Traits::Index' | |
| 20 }; | |
| 21 | |
| 22 sub Diff { | |
| 23 my ($self,$src,$dst) = @_; | |
| 24 | |
| 25 die new IMPL::InvalidArgumentException( src => "A valid source schema is required") unless is($src,schema_t); | |
| 26 die new IMPL::InvalidArgumentException( dst => "A valid desctination schema is requried" ) unless is($src,schema_t); | |
| 27 | |
| 28 my %dstTables = map { $_->name, $_ } $dst->GetTables; | |
| 29 | |
| 30 my @operations; | |
| 31 | |
| 32 foreach my $srcTable ( $src->GetTables) { | |
| 33 my $dstTable = delete $dstTables{$srcTable->name}; | |
| 34 | |
| 35 if (not $dstTable) { | |
| 36 # if a source table doesn't have a corresponding destination table, it should be deleted | |
| 37 push @operations, new IMPL::SQL::Schema::Traits::DropTable($srcTable->name); | |
| 38 } else { | |
| 39 # a source table needs to be updated | |
| 40 push @operations, $self->_DiffTables($srcTable,$dstTable); | |
| 41 } | |
| 42 | |
| 43 } | |
| 44 | |
| 45 foreach my $tbl ( values %dstTables ) { | |
| 46 push @operations, new IMPL::SQL::Schema::Traits::CreateTable( | |
| 47 new IMPL::SQL::Schema::Traits::Table( | |
| 48 $tbl->name, | |
| 49 [ map _Column2Traits($_), @{$tbl->columns} ], | |
| 50 [ map _Constraint2Traits($_), $tbl->GetConstraints()], | |
| 51 $tbl->{tag} | |
| 52 ) | |
| 53 ) | |
| 54 } | |
| 55 | |
| 56 return \@operations; | |
| 57 } | |
| 58 | |
| 59 sub _DiffTables { | |
| 60 my ($self,$src,$dst) = @_; | |
| 61 | |
| 62 my @dropConstraints; | |
| 63 my @createConstraints; | |
| 64 | |
| 65 my %srcConstraints = map { $_->name, $_ } $src->GetConstraints(); | |
| 66 my %dstConstraints = map { $_->name, $_ } $dst->GetConstraints(); | |
| 67 | |
| 68 foreach my $cnSrcName (keys %srcConstraints) { | |
| 69 if ( my $cnDst = delete $dstConstraints{$cnSrcName} ) { | |
| 70 unless ( $srcConstraints{$cnSrcName}->SameValue($cnDst) ) { | |
| 71 push @dropConstraints, | |
| 72 new IMPL::SQL::Schema::Traits::AlterTableDropConstraint( $src->name, $cnSrcName ); | |
| 73 push @createConstraints, | |
| 74 new IMPL::SQL::Schema::Traits::AlterTableAddConstraint( $dst->name, _Constraint2Traits($cnDst) ); | |
| 75 } | |
| 76 } else { | |
| 77 push @dropConstraints,new IMPL::SQL::Schema::Traits::AlterTableDropConstraint( $src->name, $cnSrcName ); | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 foreach my $cnDst (values %dstConstraints) { | |
| 82 push @createConstraints, | |
| 83 IMPL::SQL::Schema::Traits::AlterTableAddConstraint->new( $dst->name, _Constraint2Traits($cnDst) ); | |
| 84 } | |
| 85 | |
| 86 my @deleteColumns; | |
| 87 my @addColumns; | |
| 88 my @updateColumns; | |
| 89 | |
| 90 my %dstColumnIndexes = map { | |
| 91 my $col = $dst->GetColumnAt($_); | |
| 92 ($col->name, { column => $col, index => $_ }) | |
| 93 } 0 .. $dst->ColumnsCount-1; | |
| 94 | |
| 95 my @columns; | |
| 96 | |
| 97 # remove old columns, mark for update changed columns | |
| 98 for( my $i=0; $i < $src->ColumnsCount; $i++) { | |
| 99 my $colSrc = $src->GetColumnAt($i); | |
| 100 | |
| 101 if ( my $infoDst = delete $dstColumnIndexes{$colSrc->name} ) { | |
| 102 $infoDst->{prevColumn} = $colSrc; | |
| 103 push @columns,$infoDst; | |
| 104 } else { | |
| 105 push @deleteColumns,new IMPL::SQL::Schema::Traits::AlterTableDropColumn($src->name,$colSrc->name); | |
| 106 } | |
| 107 } | |
| 108 | |
| 109 #insert new columns at specified positions | |
| 110 foreach ( sort { $a->{index} <=> $b->{index} } values %dstColumnIndexes ) { | |
| 111 splice(@columns,$_->{index},0,$_); | |
| 112 push @addColumns, new IMPL::SQL::Schema::Traits::AlterTableAddColumn($src->name, _Column2Traits( $_->{column}, position => $_->{index} )); | |
| 113 } | |
| 114 | |
| 115 # remember old indexes | |
| 116 for(my $i =0; $i< @columns; $i ++) { | |
| 117 $columns[$i]->{prevIndex} = $i; | |
| 118 } | |
| 119 | |
| 120 # reorder columns | |
| 121 @columns = sort { $a->{index} <=> $b->{index} } @columns; | |
| 122 | |
| 123 foreach my $info (@columns) { | |
| 124 if ($info->{prevColumn} && ( !$info->{column}->SameValue($info->{prevColumn}) or $info->{index}!= $info->{prevIndex} ) ) { | |
| 125 my $op = new IMPL::SQL::Schema::Traits::AlterTableChangeColumn($src->name,$info->{column}->name); | |
| 126 | |
| 127 $op->position( $info->{index} ) unless $info->{prevIndex} == $info->{index}; | |
| 128 $op->isNullable( $info->{column}->isNullable ) unless equals($info->{column}->isNullable,$info->{prevColumn}->isNullable); | |
| 129 $op->defaultValue( $info->{column}->defaultValue ) unless equals($info->{column}->defaultValue, $info->{prevColumn}->defaultValue); | |
| 130 | |
| 131 my $diff = hashDiff($info->{prevColumn}->tag,$info->{column}->tag); | |
| 132 $op->options($diff) if %$diff; | |
| 133 | |
| 134 push @updateColumns, $op; | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 my @result = (@dropConstraints, @deleteColumns, @addColumns, @updateColumns, @createConstraints); | |
| 139 | |
| 140 return @result; | |
| 141 } | |
| 142 | |
| 143 sub _Column2Traits { | |
| 144 my ($column,%options) = @_; | |
| 145 | |
| 146 return new IMPL::SQL::Schema::Traits::Column( | |
| 147 $column->name, | |
| 148 $column->type, | |
| 149 isNullable => $column->isNullable, | |
| 150 defaultValue => $column->defaultValue, | |
| 151 tag => $column->tag, | |
| 152 %options | |
| 153 ); | |
| 154 } | |
| 155 | |
| 156 sub _Constraint2Traits { | |
| 157 my ($constraint) = @_; | |
| 158 | |
| 159 my $map = { | |
| 160 ConstraintForeignKey , TraitsForeignKey, | |
| 161 ConstraintPrimaryKey , TraitsPrimaryKey, | |
| 162 ConstraintUnique , TraitsUnique, | |
| 163 ConstraintIndex , TraitsIndex | |
| 164 }; | |
| 165 | |
| 166 my $class = $map->{typeof($constraint)} or die new IMPL::Exception("Can't map the constraint",typeof($constraint)); | |
| 167 | |
| 168 return $class->new( | |
| 169 $constraint->name, | |
| 170 [ map $_->name, $constraint->columns ] | |
| 171 ) | |
| 172 } | |
| 173 | |
| 174 1; |
