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