line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!perl |
2
|
|
|
|
|
|
|
# vim: set ft=perl: |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
# ------------------------------------------------------------------- |
5
|
|
|
|
|
|
|
# Copyright (C) 2002-2009 The SQLFairy Authors |
6
|
|
|
|
|
|
|
# |
7
|
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or |
8
|
|
|
|
|
|
|
# modify it under the terms of the GNU General Public License as |
9
|
|
|
|
|
|
|
# published by the Free Software Foundation; version 2. |
10
|
|
|
|
|
|
|
# |
11
|
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful, but |
12
|
|
|
|
|
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of |
13
|
|
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14
|
|
|
|
|
|
|
# General Public License for more details. |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License |
17
|
|
|
|
|
|
|
# along with this program; if not, write to the Free Software |
18
|
|
|
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19
|
|
|
|
|
|
|
# 02110-1301 USA. |
20
|
|
|
|
|
|
|
# ------------------------------------------------------------------- |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 NAME |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sqlt-diff - find the differences b/w two schemas |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 SYNOPSIS |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
For help: |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sqlt-diff -h|--help |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
For a list of all valid parsers: |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
sqlt -l|--list |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
To diff two schemas: |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
sqlt-diff [options] file_name1=parser file_name2=parser |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
Options: |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
-d|--debug Show debugging info |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 DESCRIPTION |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sqlt-diff is a utility for creating a file of SQL commands necessary to |
47
|
|
|
|
|
|
|
transform the first schema provided to the second. While not yet |
48
|
|
|
|
|
|
|
exhaustive in its ability to mutate the entire schema, it will report the |
49
|
|
|
|
|
|
|
following |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=over |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=item * New tables |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
Using the Producer class of the target (second) schema, any tables missing |
56
|
|
|
|
|
|
|
in the first schema will be generated in their entirety (fields, constraints, |
57
|
|
|
|
|
|
|
indices). |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=item * Missing/altered fields |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
Any fields missing or altered between the two schemas will be reported |
62
|
|
|
|
|
|
|
as: |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
ALTER TABLE |
65
|
|
|
|
|
|
|
[DROP ] |
66
|
|
|
|
|
|
|
[CHANGE ()] ; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=item * Missing/altered indices |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Any indices missing or of a different type or on different fields will be |
71
|
|
|
|
|
|
|
indicated. Indices that should be dropped will be reported as such: |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
DROP INDEX ON ; |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
An index of a different type or on different fields will be reported as a |
76
|
|
|
|
|
|
|
new index as such: |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
CREATE [] INDEX [] ON |
79
|
|
|
|
|
|
|
( [,] ) ; |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=back |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
"ALTER/DROP TABLE" and "CREATE INDEX" statements B generated by |
84
|
|
|
|
|
|
|
the Producer, unfortunately, and may require massaging before being passed to |
85
|
|
|
|
|
|
|
your target database. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=cut |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# ------------------------------------------------------------------- |
90
|
|
|
|
|
|
|
|
91
|
5
|
|
|
5
|
|
40
|
use strict; |
|
5
|
|
|
|
|
32
|
|
|
5
|
|
|
|
|
132
|
|
92
|
5
|
|
|
5
|
|
22
|
use warnings; |
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
116
|
|
93
|
5
|
|
|
5
|
|
2177
|
use Pod::Usage; |
|
5
|
|
|
|
|
219030
|
|
|
5
|
|
|
|
|
588
|
|
94
|
5
|
|
|
5
|
|
2649
|
use Data::Dumper; |
|
5
|
|
|
|
|
25757
|
|
|
5
|
|
|
|
|
330
|
|
95
|
5
|
|
|
5
|
|
2412
|
use SQL::Translator; |
|
5
|
|
|
|
|
17
|
|
|
5
|
|
|
|
|
154
|
|
96
|
5
|
|
|
5
|
|
28
|
use SQL::Translator::Schema::Constants; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
349
|
|
97
|
|
|
|
|
|
|
|
98
|
5
|
|
|
5
|
|
26
|
use vars qw( $VERSION ); |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
22266
|
|
99
|
5
|
|
|
|
|
17
|
$VERSION = '1.6_3'; |
100
|
|
|
|
|
|
|
|
101
|
5
|
|
|
|
|
15
|
my ( @input, $list, $help, $debug ); |
102
|
5
|
|
|
|
|
13
|
for my $arg ( @ARGV ) { |
103
|
10
|
50
|
|
|
|
94
|
if ( $arg =~ m/^-?-l(ist)?$/ ) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
104
|
0
|
|
|
|
|
0
|
$list = 1; |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
elsif ( $arg =~ m/^-?-h(elp)?$/ ) { |
107
|
0
|
|
|
|
|
0
|
$help = 1; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
elsif ( $arg =~ m/^-?-d(ebug)?$/ ) { |
110
|
0
|
|
|
|
|
0
|
$debug = 1; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
elsif ( $arg =~ m/^([^=]+)=(.+)$/ ) { |
113
|
10
|
|
|
|
|
54
|
push @input, { file => $1, parser => $2 }; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
else { |
116
|
0
|
|
|
|
|
0
|
pod2usage( msg => "Unknown argument '$arg'" ); |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
5
|
50
|
|
|
|
17
|
pod2usage(1) if $help; |
121
|
5
|
50
|
|
|
|
28
|
pod2usage('Please specify only two schemas to diff') if scalar @input > 2; |
122
|
5
|
50
|
|
|
|
16
|
pod2usage('No input') if !@input; |
123
|
|
|
|
|
|
|
|
124
|
5
|
50
|
33
|
|
|
52
|
if ( my $interactive = -t STDIN && -t STDOUT ) { |
125
|
0
|
|
|
|
|
0
|
print STDERR join("\n", |
126
|
|
|
|
|
|
|
"sqlt-diff-old is deprecated. Please sqlt-diff, and tell us ", |
127
|
|
|
|
|
|
|
"about any problems or patch SQL::Translator::Diff", |
128
|
|
|
|
|
|
|
'', |
129
|
|
|
|
|
|
|
); |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
5
|
|
|
|
|
160
|
my $tr = SQL::Translator->new; |
133
|
5
|
|
|
|
|
127
|
my @parsers = $tr->list_parsers; |
134
|
5
|
|
|
|
|
31
|
my %valid_parsers = map { $_, 1 } @parsers; |
|
120
|
|
|
|
|
170
|
|
135
|
|
|
|
|
|
|
|
136
|
5
|
50
|
|
|
|
27
|
if ( $list ) { |
137
|
0
|
|
|
|
|
0
|
print "\nParsers:\n", map { "\t$_\n" } sort @parsers; |
|
0
|
|
|
|
|
0
|
|
138
|
0
|
|
|
|
|
0
|
print "\n"; |
139
|
0
|
|
|
|
|
0
|
exit(0); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
5
|
50
|
|
|
|
17
|
pod2usage( msg => 'Too many file args' ) if @input > 2; |
143
|
|
|
|
|
|
|
|
144
|
5
|
|
|
|
|
14
|
my ( $source_schema, $source_db, $target_schema, $target_db ); |
145
|
|
|
|
|
|
|
|
146
|
5
|
|
|
|
|
10
|
my $i = 2; |
147
|
5
|
|
|
|
|
14
|
for my $in ( @input ) { |
148
|
10
|
|
|
|
|
34
|
my $file = $in->{'file'}; |
149
|
10
|
|
|
|
|
25
|
my $parser = $in->{'parser'}; |
150
|
|
|
|
|
|
|
|
151
|
10
|
50
|
|
|
|
324
|
die "Unable to read file '$file'\n" unless -r $file; |
152
|
10
|
50
|
|
|
|
43
|
die "'$parser' is an invalid parser\n" unless $valid_parsers{ $parser }; |
153
|
|
|
|
|
|
|
|
154
|
10
|
|
|
|
|
295
|
my $t = SQL::Translator->new; |
155
|
10
|
|
|
|
|
189
|
$t->debug( $debug ); |
156
|
10
|
50
|
|
|
|
193
|
$t->parser( $parser ) or die $tr->error; |
157
|
10
|
50
|
|
|
|
50
|
my $out = $t->translate( $file ) or die $tr->error; |
158
|
10
|
|
|
|
|
178
|
my $schema = $t->schema; |
159
|
10
|
50
|
|
|
|
97
|
unless ( $schema->name ) { |
160
|
10
|
|
|
|
|
41
|
$schema->name( $file ); |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
10
|
100
|
|
|
|
34
|
if ( $i == 1 ) { |
164
|
5
|
|
|
|
|
14
|
$source_schema = $schema; |
165
|
5
|
|
|
|
|
12
|
$source_db = $parser; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
else { |
168
|
5
|
|
|
|
|
11
|
$target_schema = $schema; |
169
|
5
|
|
|
|
|
11
|
$target_db = $parser; |
170
|
|
|
|
|
|
|
} |
171
|
10
|
|
|
|
|
149
|
$i--; |
172
|
|
|
|
|
|
|
} |
173
|
5
|
|
|
|
|
16
|
my $case_insensitive = $target_db =~ /SQLServer/; |
174
|
|
|
|
|
|
|
|
175
|
5
|
|
|
|
|
28
|
my $s1_name = $source_schema->name; |
176
|
5
|
|
|
|
|
38
|
my $s2_name = $target_schema->name; |
177
|
5
|
|
|
|
|
47
|
my ( @new_tables, @diffs , @diffs_at_end); |
178
|
5
|
|
|
|
|
48
|
for my $t1 ( $source_schema->get_tables ) { |
179
|
11
|
|
|
|
|
203
|
my $t1_name = $t1->name; |
180
|
11
|
|
|
|
|
246
|
my $t2 = $target_schema->get_table( $t1_name, $case_insensitive ); |
181
|
|
|
|
|
|
|
|
182
|
11
|
50
|
|
|
|
36
|
warn "TABLE '$s1_name.$t1_name'\n" if $debug; |
183
|
11
|
100
|
|
|
|
230
|
unless ( $t2 ) { |
184
|
1
|
50
|
|
|
|
5
|
warn "Couldn't find table '$s1_name.$t1_name' in '$s2_name'\n" |
185
|
|
|
|
|
|
|
if $debug; |
186
|
1
|
50
|
|
|
|
4
|
if ( $target_db =~ /(SQLServer|Oracle)/ ) { |
187
|
0
|
|
|
|
|
0
|
for my $constraint ( $t1->get_constraints ) { |
188
|
0
|
0
|
|
|
|
0
|
next if $constraint->type ne FOREIGN_KEY; |
189
|
0
|
|
|
|
|
0
|
push @diffs_at_end, "ALTER TABLE $t1_name ADD ". |
190
|
|
|
|
|
|
|
constraint_to_string($constraint, $source_schema).";"; |
191
|
0
|
|
|
|
|
0
|
$t1->drop_constraint($constraint); |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
} |
194
|
1
|
|
|
|
|
3
|
push @new_tables, $t1; |
195
|
1
|
|
|
|
|
4
|
next; |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# Go through our options |
199
|
10
|
|
|
|
|
180
|
my $options_different = 0; |
200
|
10
|
|
|
|
|
20
|
my %checkedOptions; |
201
|
|
|
|
|
|
|
OPTION: |
202
|
10
|
|
|
|
|
197
|
for my $t1_option_ref ( $t1->options ) { |
203
|
4
|
|
|
|
|
13
|
my($key1, $value1) = %{$t1_option_ref}; |
|
4
|
|
|
|
|
13
|
|
204
|
4
|
|
|
|
|
64
|
for my $t2_option_ref ( $t2->options ) { |
205
|
4
|
|
|
|
|
9
|
my($key2, $value2) = %{$t2_option_ref}; |
|
4
|
|
|
|
|
13
|
|
206
|
4
|
50
|
|
|
|
15
|
if ( $key1 eq $key2 ) { |
207
|
4
|
50
|
|
|
|
14
|
if ( defined $value1 != defined $value2 ) { |
208
|
0
|
|
|
|
|
0
|
$options_different = 1; |
209
|
0
|
|
|
|
|
0
|
last OPTION; |
210
|
|
|
|
|
|
|
} |
211
|
4
|
100
|
66
|
|
|
25
|
if ( defined $value1 && $value1 ne $value2 ) { |
212
|
1
|
|
|
|
|
2
|
$options_different = 1; |
213
|
1
|
|
|
|
|
4
|
last OPTION; |
214
|
|
|
|
|
|
|
} |
215
|
3
|
|
|
|
|
9
|
$checkedOptions{$key1} = 1; |
216
|
3
|
|
|
|
|
10
|
next OPTION; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
} |
219
|
0
|
|
|
|
|
0
|
$options_different = 1; |
220
|
0
|
|
|
|
|
0
|
last OPTION; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
# Go through the other table's options |
223
|
10
|
100
|
|
|
|
29
|
unless ( $options_different ) { |
224
|
9
|
|
|
|
|
159
|
for my $t2_option_ref ( $t2->options ) { |
225
|
3
|
|
|
|
|
8
|
my($key, $value) = %{$t2_option_ref}; |
|
3
|
|
|
|
|
10
|
|
226
|
3
|
50
|
|
|
|
13
|
next if $checkedOptions{$key}; |
227
|
0
|
|
|
|
|
0
|
$options_different = 1; |
228
|
0
|
|
|
|
|
0
|
last; |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
# If there's a difference, just re-set all the options |
232
|
10
|
|
|
|
|
22
|
my @diffs_table_options; |
233
|
10
|
100
|
|
|
|
29
|
if ( $options_different ) { |
234
|
1
|
|
|
|
|
1
|
my @options = (); |
235
|
1
|
|
|
|
|
17
|
foreach my $option_ref ( $t1->options ) { |
236
|
1
|
|
|
|
|
3
|
my($key, $value) = %{$option_ref}; |
|
1
|
|
|
|
|
4
|
|
237
|
1
|
50
|
|
|
|
5
|
push(@options, defined $value ? "$key=$value" : $key); |
238
|
|
|
|
|
|
|
} |
239
|
1
|
|
|
|
|
4
|
my $options = join(' ', @options); |
240
|
1
|
|
|
|
|
3
|
@diffs_table_options = ("ALTER TABLE $t1_name $options;"); |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
10
|
|
|
|
|
366
|
my $t2_name = $t2->name; |
244
|
10
|
|
|
|
|
166
|
my(@diffs_table_adds, @diffs_table_changes); |
245
|
10
|
|
|
|
|
42
|
for my $t1_field ( $t1->get_fields ) { |
246
|
42
|
|
|
|
|
139
|
my $f1_type = $t1_field->data_type; |
247
|
42
|
|
|
|
|
804
|
my $f1_size = $t1_field->size; |
248
|
42
|
|
|
|
|
979
|
my $f1_name = $t1_field->name; |
249
|
42
|
|
|
|
|
1295
|
my $f1_nullable = $t1_field->is_nullable; |
250
|
42
|
|
|
|
|
2140
|
my $f1_default = $t1_field->default_value; |
251
|
42
|
|
|
|
|
599
|
my $f1_auto_inc = $t1_field->is_auto_increment; |
252
|
42
|
|
|
|
|
495
|
my $t2_field = $t2->get_field( $f1_name, $case_insensitive ); |
253
|
42
|
|
|
|
|
331
|
my $f1_full_name = "$s1_name.$t1_name.$t1_name"; |
254
|
42
|
50
|
|
|
|
97
|
warn "FIELD '$f1_full_name'\n" if $debug; |
255
|
|
|
|
|
|
|
|
256
|
42
|
|
|
|
|
109
|
my $f2_full_name = "$s2_name.$t2_name.$f1_name"; |
257
|
|
|
|
|
|
|
|
258
|
42
|
100
|
|
|
|
319
|
unless ( $t2_field ) { |
259
|
3
|
50
|
|
|
|
11
|
warn "Couldn't find field '$f2_full_name' in '$t2_name'\n" |
260
|
|
|
|
|
|
|
if $debug; |
261
|
3
|
|
|
|
|
6
|
my $temp_default_value = 0; |
262
|
3
|
0
|
33
|
|
|
24
|
if ( $target_db =~ /SQLServer/ && !$f1_nullable && !defined $f1_default ) { |
|
|
|
33
|
|
|
|
|
263
|
|
|
|
|
|
|
# SQL Server doesn't allow adding non-nullable, non-default columns |
264
|
|
|
|
|
|
|
# so we add it with a default value, then remove the default value |
265
|
0
|
|
|
|
|
0
|
$temp_default_value = 1; |
266
|
0
|
|
|
|
|
0
|
my(@numeric_types) = qw(decimal numeric float real int bigint smallint tinyint); |
267
|
0
|
0
|
|
|
|
0
|
$f1_default = grep($_ eq $f1_type, @numeric_types) ? 0 : ''; |
268
|
|
|
|
|
|
|
} |
269
|
3
|
50
|
100
|
|
|
61
|
push @diffs_table_adds, sprintf( "ALTER TABLE %s ADD %s%s %s%s%s%s%s%s;", |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
270
|
|
|
|
|
|
|
$t1_name, $target_db =~ /Oracle/ ? '(' : '', |
271
|
|
|
|
|
|
|
$f1_name, $f1_type, |
272
|
|
|
|
|
|
|
($f1_size && $f1_type !~ /(blob|text)$/) ? "($f1_size)" : '', |
273
|
|
|
|
|
|
|
!defined $f1_default ? '' |
274
|
|
|
|
|
|
|
: uc $f1_default eq 'NULL' ? ' DEFAULT NULL' |
275
|
|
|
|
|
|
|
: uc $f1_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' |
276
|
|
|
|
|
|
|
: " DEFAULT '$f1_default'", |
277
|
|
|
|
|
|
|
$f1_nullable ? '' : ' NOT NULL', |
278
|
|
|
|
|
|
|
$f1_auto_inc ? ' AUTO_INCREMENT' : '', |
279
|
|
|
|
|
|
|
$target_db =~ /Oracle/ ? ')' : '', |
280
|
|
|
|
|
|
|
); |
281
|
3
|
50
|
|
|
|
10
|
if ( $temp_default_value ) { |
282
|
0
|
|
|
|
|
0
|
undef $f1_default; |
283
|
0
|
|
|
|
|
0
|
push @diffs_table_adds, sprintf( <
|
284
|
|
|
|
|
|
|
DECLARE \@defname VARCHAR(100), \@cmd VARCHAR(1000) |
285
|
|
|
|
|
|
|
SET \@defname = |
286
|
|
|
|
|
|
|
(SELECT name |
287
|
|
|
|
|
|
|
FROM sysobjects so JOIN sysconstraints sc |
288
|
|
|
|
|
|
|
ON so.id = sc.constid |
289
|
|
|
|
|
|
|
WHERE object_name(so.parent_obj) = '%s' |
290
|
|
|
|
|
|
|
AND so.xtype = 'D' |
291
|
|
|
|
|
|
|
AND sc.colid = |
292
|
|
|
|
|
|
|
(SELECT colid FROM syscolumns |
293
|
|
|
|
|
|
|
WHERE id = object_id('%s') AND |
294
|
|
|
|
|
|
|
name = '%s')) |
295
|
|
|
|
|
|
|
SET \@cmd = 'ALTER TABLE %s DROP CONSTRAINT ' |
296
|
|
|
|
|
|
|
+ \@defname |
297
|
|
|
|
|
|
|
EXEC(\@cmd) |
298
|
|
|
|
|
|
|
END |
299
|
|
|
|
|
|
|
, $t1_name, $t1_name, $f1_name, $t1_name, |
300
|
|
|
|
|
|
|
); |
301
|
|
|
|
|
|
|
} |
302
|
3
|
|
|
|
|
9
|
next; |
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
|
305
|
39
|
|
|
|
|
700
|
my $f2_type = $t2_field->data_type; |
306
|
39
|
|
100
|
|
|
634
|
my $f2_size = $t2_field->size || ''; |
307
|
39
|
|
|
|
|
1003
|
my $f2_nullable = $t2_field->is_nullable; |
308
|
39
|
|
|
|
|
1810
|
my $f2_default = $t2_field->default_value; |
309
|
39
|
|
|
|
|
586
|
my $f2_auto_inc = $t2_field->is_auto_increment; |
310
|
39
|
100
|
|
|
|
957
|
if ( !$t1_field->equals($t2_field, $case_insensitive) ) { |
311
|
|
|
|
|
|
|
# SQLServer timestamp fields can't be altered, so we drop and add instead |
312
|
6
|
50
|
33
|
|
|
79
|
if ( $target_db =~ /SQLServer/ && $f2_type eq "timestamp" ) { |
313
|
0
|
|
|
|
|
0
|
push @diffs_table_changes, "ALTER TABLE $t1_name DROP COLUMN $f1_name;"; |
314
|
0
|
0
|
0
|
|
|
0
|
push @diffs_table_changes, sprintf( "ALTER TABLE %s ADD %s%s %s%s%s%s%s%s;", |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
315
|
|
|
|
|
|
|
$t1_name, $target_db =~ /Oracle/ ? '(' : '', |
316
|
|
|
|
|
|
|
$f1_name, $f1_type, |
317
|
|
|
|
|
|
|
($f1_size && $f1_type !~ /(blob|text)$/) ? "($f1_size)" : '', |
318
|
|
|
|
|
|
|
!defined $f1_default ? '' |
319
|
|
|
|
|
|
|
: uc $f1_default eq 'NULL' ? ' DEFAULT NULL' |
320
|
|
|
|
|
|
|
: uc $f1_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' |
321
|
|
|
|
|
|
|
: " DEFAULT '$f1_default'", |
322
|
|
|
|
|
|
|
$f1_nullable ? '' : ' NOT NULL', |
323
|
|
|
|
|
|
|
$f1_auto_inc ? ' AUTO_INCREMENT' : '', |
324
|
|
|
|
|
|
|
$target_db =~ /Oracle/ ? ')' : '', |
325
|
|
|
|
|
|
|
); |
326
|
0
|
|
|
|
|
0
|
next; |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
6
|
50
|
|
|
|
19
|
my $changeText = $target_db =~ /SQLServer/ ? 'ALTER COLUMN' : |
|
|
50
|
|
|
|
|
|
330
|
|
|
|
|
|
|
$target_db =~ /Oracle/ ? 'MODIFY (' : 'CHANGE'; |
331
|
6
|
100
|
|
|
|
13
|
my $nullText = $f1_nullable ? '' : ' NOT NULL'; |
332
|
6
|
50
|
33
|
|
|
16
|
$nullText = '' if $target_db =~ /Oracle/ && $f1_nullable == $f2_nullable; |
333
|
6
|
100
|
66
|
|
|
98
|
push @diffs_table_changes, sprintf( "ALTER TABLE %s %s %s%s %s%s%s%s%s%s;", |
|
|
100
|
66
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
334
|
|
|
|
|
|
|
$t1_name, $changeText, |
335
|
|
|
|
|
|
|
$f1_name, $target_db =~ /MySQL/ ? " $f1_name" : '', |
336
|
|
|
|
|
|
|
$f1_type, ($f1_size && $f1_type !~ /(blob|text)$/) ? "($f1_size)" : '', |
337
|
|
|
|
|
|
|
$nullText, |
338
|
|
|
|
|
|
|
!defined $f1_default || $target_db =~ /SQLServer/ ? '' |
339
|
|
|
|
|
|
|
: uc $f1_default eq 'NULL' ? ' DEFAULT NULL' |
340
|
|
|
|
|
|
|
: uc $f1_default eq 'CURRENT_TIMESTAMP' ? ' DEFAULT CURRENT_TIMESTAMP' |
341
|
|
|
|
|
|
|
: " DEFAULT '$f1_default'", |
342
|
|
|
|
|
|
|
$f1_auto_inc ? ' AUTO_INCREMENT' : '', |
343
|
|
|
|
|
|
|
$target_db =~ /Oracle/ ? ')' : '', |
344
|
|
|
|
|
|
|
); |
345
|
6
|
50
|
66
|
|
|
30
|
if ( defined $f1_default && $target_db =~ /SQLServer/ ) { |
346
|
|
|
|
|
|
|
# Adding a column with a default value for SQL Server means adding a |
347
|
|
|
|
|
|
|
# constraint and setting existing NULLs to the default value |
348
|
0
|
0
|
|
|
|
0
|
push @diffs_table_changes, sprintf( "ALTER TABLE %s ADD CONSTRAINT DF_%s_%s %s FOR %s;", |
|
|
0
|
|
|
|
|
|
349
|
|
|
|
|
|
|
$t1_name, $t1_name, $f1_name, uc $f1_default eq 'NULL' ? 'DEFAULT NULL' |
350
|
|
|
|
|
|
|
: uc $f1_default eq 'CURRENT_TIMESTAMP' ? 'DEFAULT CURRENT_TIMESTAMP' |
351
|
|
|
|
|
|
|
: "DEFAULT '$f1_default'", $f1_name, |
352
|
|
|
|
|
|
|
); |
353
|
0
|
0
|
|
|
|
0
|
push @diffs_table_changes, sprintf( "UPDATE %s SET %s = %s WHERE %s IS NULL;", |
|
|
0
|
|
|
|
|
|
354
|
|
|
|
|
|
|
$t1_name, $f1_name, uc $f1_default eq 'NULL' ? 'NULL' |
355
|
|
|
|
|
|
|
: uc $f1_default eq 'CURRENT_TIMESTAMP' ? 'CURRENT_TIMESTAMP' |
356
|
|
|
|
|
|
|
: "'$f1_default'", $f1_name, |
357
|
|
|
|
|
|
|
); |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
|
362
|
10
|
|
|
|
|
35
|
my(%checked_indices, @diffs_index_creates, @diffs_index_drops); |
363
|
|
|
|
|
|
|
INDEX: |
364
|
10
|
|
|
|
|
45
|
for my $i1 ( $t1->get_indices ) { |
365
|
2
|
|
|
|
|
31
|
for my $i2 ( $t2->get_indices ) { |
366
|
2
|
100
|
|
|
|
51
|
if ( $i1->equals($i2, $case_insensitive) ) { |
367
|
1
|
|
|
|
|
5
|
$checked_indices{$i2} = 1; |
368
|
1
|
|
|
|
|
5
|
next INDEX; |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
} |
371
|
1
|
50
|
|
|
|
28
|
push @diffs_index_creates, sprintf( |
|
|
50
|
|
|
|
|
|
372
|
|
|
|
|
|
|
"CREATE %sINDEX%s ON %s (%s);", |
373
|
|
|
|
|
|
|
$i1->type eq NORMAL ? '' : $i1->type." ", |
374
|
|
|
|
|
|
|
$i1->name ? " ".$i1->name : '', |
375
|
|
|
|
|
|
|
$t1_name, |
376
|
|
|
|
|
|
|
join(",", $i1->fields), |
377
|
|
|
|
|
|
|
); |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
INDEX2: |
380
|
10
|
|
|
|
|
34
|
for my $i2 ( $t2->get_indices ) { |
381
|
2
|
100
|
|
|
|
24
|
next if $checked_indices{$i2}; |
382
|
1
|
|
|
|
|
4
|
for my $i1 ( $t1->get_indices ) { |
383
|
1
|
50
|
|
|
|
21
|
next INDEX2 if $i2->equals($i1, $case_insensitive); |
384
|
|
|
|
|
|
|
} |
385
|
1
|
50
|
|
|
|
26
|
$target_db =~ /SQLServer/ |
386
|
|
|
|
|
|
|
? push @diffs_index_drops, "DROP INDEX $t1_name.".$i2->name.";" |
387
|
|
|
|
|
|
|
: push @diffs_index_drops, "DROP INDEX ".$i2->name." on $t1_name;"; |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
|
390
|
10
|
|
|
|
|
28
|
my(%checked_constraints, @diffs_constraint_drops); |
391
|
|
|
|
|
|
|
CONSTRAINT: |
392
|
10
|
|
|
|
|
37
|
for my $c1 ( $t1->get_constraints ) { |
393
|
22
|
50
|
66
|
|
|
190
|
next if $source_db =~ /Oracle/ && $c1->type eq UNIQUE && $c1->name =~ /^SYS_/i; |
|
|
|
33
|
|
|
|
|
394
|
22
|
|
|
|
|
104
|
for my $c2 ( $t2->get_constraints ) { |
395
|
42
|
100
|
|
|
|
1231
|
if ( $c1->equals($c2, $case_insensitive) ) { |
396
|
17
|
|
|
|
|
59
|
$checked_constraints{$c2} = 1; |
397
|
17
|
|
|
|
|
66
|
next CONSTRAINT; |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
} |
400
|
5
|
|
|
|
|
70
|
push @diffs_at_end, "ALTER TABLE $t1_name ADD ". |
401
|
|
|
|
|
|
|
constraint_to_string($c1, $source_schema).";"; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
CONSTRAINT2: |
404
|
10
|
|
|
|
|
47
|
for my $c2 ( $t2->get_constraints ) { |
405
|
22
|
100
|
|
|
|
160
|
next if $checked_constraints{$c2}; |
406
|
5
|
|
|
|
|
19
|
for my $c1 ( $t1->get_constraints ) { |
407
|
13
|
50
|
|
|
|
326
|
next CONSTRAINT2 if $c2->equals($c1, $case_insensitive); |
408
|
|
|
|
|
|
|
} |
409
|
5
|
100
|
|
|
|
130
|
if ( $c2->type eq UNIQUE ) { |
|
|
50
|
|
|
|
|
|
410
|
1
|
|
|
|
|
35
|
push @diffs_constraint_drops, "ALTER TABLE $t1_name DROP INDEX ". |
411
|
|
|
|
|
|
|
$c2->name.";"; |
412
|
|
|
|
|
|
|
} elsif ( $target_db =~ /SQLServer/ ) { |
413
|
0
|
|
|
|
|
0
|
push @diffs_constraint_drops, "ALTER TABLE $t1_name DROP ".$c2->name.";"; |
414
|
|
|
|
|
|
|
} else { |
415
|
4
|
100
|
|
|
|
141
|
push @diffs_constraint_drops, "ALTER TABLE $t1_name DROP ".$c2->type. |
416
|
|
|
|
|
|
|
($c2->type eq FOREIGN_KEY ? " ".$c2->name : '').";"; |
417
|
|
|
|
|
|
|
} |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
|
420
|
10
|
|
|
|
|
63
|
push @diffs, @diffs_index_drops, @diffs_constraint_drops, |
421
|
|
|
|
|
|
|
@diffs_table_options, @diffs_table_adds, |
422
|
|
|
|
|
|
|
@diffs_table_changes, @diffs_index_creates; |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
5
|
|
|
|
|
33
|
for my $t2 ( $target_schema->get_tables ) { |
426
|
11
|
|
|
|
|
242
|
my $t2_name = $t2->name; |
427
|
11
|
|
|
|
|
207
|
my $t1 = $source_schema->get_table( $t2_name, $target_db =~ /SQLServer/ ); |
428
|
|
|
|
|
|
|
|
429
|
11
|
100
|
|
|
|
40
|
unless ( $t1 ) { |
430
|
1
|
50
|
|
|
|
7
|
if ( $target_db =~ /SQLServer/ ) { |
431
|
0
|
|
|
|
|
0
|
for my $constraint ( $t2->get_constraints ) { |
432
|
0
|
0
|
|
|
|
0
|
next if $constraint->type eq PRIMARY_KEY; |
433
|
0
|
|
|
|
|
0
|
push @diffs, "ALTER TABLE $t2_name DROP ".$constraint->name.";"; |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
} |
436
|
1
|
|
|
|
|
21
|
push @diffs_at_end, "DROP TABLE $t2_name;"; |
437
|
1
|
|
|
|
|
4
|
next; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
|
440
|
10
|
|
|
|
|
189
|
for my $t2_field ( $t2->get_fields ) { |
441
|
41
|
|
|
|
|
1088
|
my $f2_name = $t2_field->name; |
442
|
41
|
|
|
|
|
657
|
my $t1_field = $t1->get_field( $f2_name ); |
443
|
41
|
100
|
|
|
|
633
|
unless ( $t1_field ) { |
444
|
2
|
50
|
|
|
|
8
|
my $modifier = $target_db =~ /SQLServer/ ? "COLUMN " : ''; |
445
|
2
|
|
|
|
|
11
|
push @diffs, "ALTER TABLE $t2_name DROP $modifier$f2_name;"; |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
|
450
|
5
|
100
|
|
|
|
85
|
if ( @new_tables ) { |
451
|
1
|
|
|
|
|
27
|
my $dummy_tr = SQL::Translator->new; |
452
|
1
|
|
|
|
|
50
|
$dummy_tr->schema->add_table( $_ ) for @new_tables; |
453
|
1
|
|
|
|
|
18
|
my $producer = $dummy_tr->producer( $target_db ); |
454
|
1
|
|
|
|
|
6
|
unshift @diffs, $producer->( $dummy_tr ); |
455
|
|
|
|
|
|
|
} |
456
|
5
|
|
|
|
|
17
|
push(@diffs, @diffs_at_end); |
457
|
|
|
|
|
|
|
|
458
|
5
|
100
|
|
|
|
18
|
if ( @diffs ) { |
459
|
3
|
100
|
|
|
|
228
|
if ( $source_db !~ /^(MySQL|SQLServer|Oracle)$/ ) { |
460
|
1
|
|
|
|
|
7
|
unshift(@diffs, "-- Target database $target_db is untested/unsupported!!!"); |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
5
|
100
|
|
|
|
18
|
if ( @diffs ) { |
465
|
3
|
|
|
|
|
44
|
print join( "\n", |
466
|
|
|
|
|
|
|
"-- Convert schema '$s2_name' to '$s1_name':\n", @diffs, "\n" |
467
|
|
|
|
|
|
|
); |
468
|
3
|
|
|
|
|
869
|
exit(1); |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
else { |
471
|
2
|
|
|
|
|
817
|
print "There were no differences.\n"; |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
sub constraint_to_string { |
475
|
5
|
|
|
5
|
|
11
|
my $c = shift; |
476
|
5
|
50
|
|
|
|
13
|
my $schema = shift or die "No schema given"; |
477
|
5
|
100
|
|
|
|
84
|
my @fields = $c->field_names or return ''; |
478
|
|
|
|
|
|
|
|
479
|
4
|
50
|
|
|
|
65
|
if ( $c->type eq PRIMARY_KEY ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
480
|
0
|
0
|
|
|
|
0
|
if ( $target_db =~ /Oracle/ ) { |
481
|
0
|
0
|
|
|
|
0
|
return (defined $c->name ? 'CONSTRAINT '.$c->name.' ' : '') . |
482
|
|
|
|
|
|
|
'PRIMARY KEY (' . join(', ', @fields). ')'; |
483
|
|
|
|
|
|
|
} else { |
484
|
0
|
|
|
|
|
0
|
return 'PRIMARY KEY (' . join(', ', @fields). ')'; |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
elsif ( $c->type eq UNIQUE ) { |
488
|
2
|
50
|
|
|
|
37
|
if ( $target_db =~ /Oracle/ ) { |
489
|
0
|
0
|
|
|
|
0
|
return (defined $c->name ? 'CONSTRAINT '.$c->name.' ' : '') . |
490
|
|
|
|
|
|
|
'UNIQUE (' . join(', ', @fields). ')'; |
491
|
|
|
|
|
|
|
} else { |
492
|
2
|
50
|
|
|
|
37
|
return 'UNIQUE '. |
493
|
|
|
|
|
|
|
(defined $c->name ? $c->name.' ' : ''). |
494
|
|
|
|
|
|
|
'(' . join(', ', @fields). ')'; |
495
|
|
|
|
|
|
|
} |
496
|
|
|
|
|
|
|
} |
497
|
|
|
|
|
|
|
elsif ( $c->type eq FOREIGN_KEY ) { |
498
|
|
|
|
|
|
|
my $def = join(' ', |
499
|
2
|
50
|
|
|
|
77
|
map { $_ || () } 'CONSTRAINT', $c->name, 'FOREIGN KEY' |
|
6
|
|
|
|
|
23
|
|
500
|
|
|
|
|
|
|
); |
501
|
|
|
|
|
|
|
|
502
|
2
|
|
|
|
|
11
|
$def .= ' (' . join( ', ', @fields ) . ')'; |
503
|
|
|
|
|
|
|
|
504
|
2
|
|
|
|
|
13
|
$def .= ' REFERENCES ' . $c->reference_table; |
505
|
|
|
|
|
|
|
|
506
|
2
|
50
|
|
|
|
37
|
my @rfields = map { $_ || () } $c->reference_fields; |
|
2
|
|
|
|
|
15
|
|
507
|
2
|
50
|
|
|
|
9
|
unless ( @rfields ) { |
508
|
0
|
|
|
|
|
0
|
my $rtable_name = $c->reference_table; |
509
|
0
|
0
|
|
|
|
0
|
if ( my $ref_table = $schema->get_table( $rtable_name ) ) { |
510
|
0
|
|
|
|
|
0
|
push @rfields, $ref_table->primary_key; |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
else { |
513
|
0
|
|
|
|
|
0
|
warn "Can't find reference table '$rtable_name' " . |
514
|
|
|
|
|
|
|
"in schema\n"; |
515
|
|
|
|
|
|
|
} |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
|
518
|
2
|
50
|
|
|
|
8
|
if ( @rfields ) { |
519
|
2
|
|
|
|
|
9
|
$def .= ' (' . join( ', ', @rfields ) . ')'; |
520
|
|
|
|
|
|
|
} |
521
|
|
|
|
|
|
|
else { |
522
|
0
|
|
|
|
|
0
|
warn "FK constraint on " . 'some table' . '.' . |
523
|
|
|
|
|
|
|
join('', @fields) . " has no reference fields\n"; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
2
|
50
|
|
|
|
40
|
if ( $c->match_type ) { |
527
|
0
|
0
|
|
|
|
0
|
$def .= ' MATCH ' . |
528
|
|
|
|
|
|
|
( $c->match_type =~ /full/i ) ? 'FULL' : 'PARTIAL'; |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
|
531
|
2
|
100
|
|
|
|
76
|
if ( $c->on_delete ) { |
532
|
1
|
|
|
|
|
17
|
$def .= ' ON DELETE '.join( ' ', $c->on_delete ); |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
2
|
50
|
|
|
|
35
|
if ( $c->on_update ) { |
536
|
0
|
|
|
|
|
0
|
$def .= ' ON UPDATE '.join( ' ', $c->on_update ); |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
|
539
|
2
|
|
|
|
|
13
|
return $def; |
540
|
|
|
|
|
|
|
} |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
# ------------------------------------------------------------------- |
544
|
|
|
|
|
|
|
# Bring out number weight & measure in a year of dearth. |
545
|
|
|
|
|
|
|
# William Blake |
546
|
|
|
|
|
|
|
# ------------------------------------------------------------------- |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
=pod |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=head1 AUTHOR |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
Ken Youens-Clark Ekclark@cpan.orgE. |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
=head1 SEE ALSO |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
SQL::Translator, L. |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
=cut |