nando 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.env +2 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +47 -0
- data/LICENSE +201 -0
- data/README.md +49 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/nando +83 -0
- data/lib/nando/baseline_templates/migration.rb +9 -0
- data/lib/nando/errors.rb +13 -0
- data/lib/nando/generator.rb +86 -0
- data/lib/nando/interface.rb +87 -0
- data/lib/nando/logger.rb +30 -0
- data/lib/nando/migration.rb +347 -0
- data/lib/nando/migrator.rb +369 -0
- data/lib/nando/parser.rb +68 -0
- data/lib/nando/parser_templates/migration.rb +13 -0
- data/lib/nando/schema_diff.rb +805 -0
- data/lib/nando/templates/migration.rb +9 -0
- data/lib/nando/templates/migration_without_transaction.rb +9 -0
- data/lib/nando/updater.rb +372 -0
- data/lib/nando/utils.rb +22 -0
- data/lib/nando/version.rb +3 -0
- data/lib/nando.rb +12 -0
- data/nando.gemspec +44 -0
- data/notes.txt +128 -0
- metadata +200 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
module NandoSchemaDiff
|
|
2
|
+
|
|
3
|
+
SCHEMA_PLACEHOLDER = '___SCHEMANAME___'
|
|
4
|
+
TABLE_TYPE = {
|
|
5
|
+
'r' => :tables,
|
|
6
|
+
'v' => :views
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
def self.diff_schemas (source_schema, target_schema)
|
|
10
|
+
|
|
11
|
+
@schema_variable = NandoMigrator.instance.schema_variable
|
|
12
|
+
|
|
13
|
+
source_info = get_info_base_structure()
|
|
14
|
+
target_info = get_info_base_structure()
|
|
15
|
+
|
|
16
|
+
source = get_schema_structure(source_schema)
|
|
17
|
+
target = get_schema_structure(target_schema)
|
|
18
|
+
|
|
19
|
+
# start comparing structure
|
|
20
|
+
|
|
21
|
+
# checking for different tables
|
|
22
|
+
check_different_tables(source[:tables], target[:tables], source_info, target_info)
|
|
23
|
+
check_different_tables(target[:tables], source[:tables], target_info, source_info)
|
|
24
|
+
|
|
25
|
+
# checking for different views
|
|
26
|
+
check_different_views(source[:views], target[:views], source_info, target_info)
|
|
27
|
+
check_different_views(target[:views], source[:views], target_info, source_info)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# checking for different columns in all shared tables
|
|
31
|
+
check_different_columns(source[:tables], target[:tables], source_info, target_info)
|
|
32
|
+
check_different_columns(target[:tables], source[:tables], target_info, source_info)
|
|
33
|
+
|
|
34
|
+
# checking for mismatching columns in all shared tables
|
|
35
|
+
check_mismatching_columns(source[:tables], target[:tables], source_info, target_info)
|
|
36
|
+
check_mismatching_columns(target[:tables], source[:tables], target_info, source_info)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# checking for different triggers in all shared tables
|
|
40
|
+
check_different_triggers(source[:tables], target[:tables], source_info, target_info)
|
|
41
|
+
check_different_triggers(target[:tables], source[:tables], target_info, source_info)
|
|
42
|
+
|
|
43
|
+
# checking for mismatching triggers in all shared tables
|
|
44
|
+
check_mismatching_triggers(source[:tables], target[:tables], source_info, target_info)
|
|
45
|
+
check_mismatching_triggers(target[:tables], source[:tables], target_info, source_info)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# checking for different constraints in all shared tables
|
|
49
|
+
check_different_constraints(source[:tables], target[:tables], source_info, target_info)
|
|
50
|
+
check_different_constraints(target[:tables], source[:tables], target_info, source_info)
|
|
51
|
+
|
|
52
|
+
# checking for mismatching constraints in all shared tables
|
|
53
|
+
check_mismatching_constraints(source[:tables], target[:tables], source_info, target_info)
|
|
54
|
+
check_mismatching_constraints(target[:tables], source[:tables], target_info, source_info)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# checking for different indexes in all shared tables
|
|
58
|
+
check_different_indexes(source[:tables], target[:tables], source_info, target_info)
|
|
59
|
+
check_different_indexes(target[:tables], source[:tables], target_info, source_info)
|
|
60
|
+
|
|
61
|
+
# checking for mismatching indexes in all shared tables
|
|
62
|
+
check_mismatching_indexes(source[:tables], target[:tables], source_info, target_info)
|
|
63
|
+
check_mismatching_indexes(target[:tables], source[:tables], target_info, source_info)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
source_suggestions = print_diff_info(source_info, @schema_variable, source_schema, target_schema)
|
|
67
|
+
target_suggestions = print_diff_info(target_info, @schema_variable, target_schema, source_schema)
|
|
68
|
+
|
|
69
|
+
# TODO: might skip this if there is no diff
|
|
70
|
+
|
|
71
|
+
wants_suggestions = NandoInterface.get_user_input_boolean("Do want to see the suggestions for changing the schema?")
|
|
72
|
+
if !wants_suggestions
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# suggestions
|
|
77
|
+
puts "\n\n===========================//===========================\n".magenta.bold
|
|
78
|
+
puts "\nSuggestion for ".magenta.bold + "'up'".white.bold + ":".magenta.bold
|
|
79
|
+
print_schema_correction_suggestions(@schema_variable, source_suggestions)
|
|
80
|
+
|
|
81
|
+
puts "\nSuggestion for ".magenta.bold + "'down'".white.bold + ":".magenta.bold
|
|
82
|
+
print_schema_correction_suggestions(@schema_variable, target_suggestions)
|
|
83
|
+
puts ""
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.get_schema_structure (curr_schema)
|
|
87
|
+
schema_structure = {
|
|
88
|
+
:tables => {},
|
|
89
|
+
:views => {}
|
|
90
|
+
}
|
|
91
|
+
db_connection = NandoMigrator.instance.get_database_connection()
|
|
92
|
+
|
|
93
|
+
# get all tables/views in the schema
|
|
94
|
+
results = db_connection.exec("
|
|
95
|
+
SELECT n.nspname AS table_schema,
|
|
96
|
+
t.relname AS table_name,
|
|
97
|
+
t.relkind AS table_type
|
|
98
|
+
FROM pg_class t
|
|
99
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
100
|
+
WHERE t.relkind IN ('r', 'v')
|
|
101
|
+
AND n.nspname = '#{curr_schema}'
|
|
102
|
+
")
|
|
103
|
+
|
|
104
|
+
for row in results do
|
|
105
|
+
schema_structure[TABLE_TYPE[row['table_type']]][row['table_name']] = {
|
|
106
|
+
:columns => {},
|
|
107
|
+
:triggers => {},
|
|
108
|
+
:constraints => {},
|
|
109
|
+
:indexes => {}
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# get all columns for each table/view
|
|
114
|
+
results = db_connection.exec("
|
|
115
|
+
SELECT n.nspname AS table_schema,
|
|
116
|
+
t.relname AS table_name,
|
|
117
|
+
t.relkind AS table_type,
|
|
118
|
+
a.attname AS column_name,
|
|
119
|
+
a.atthasdef AS column_has_default,
|
|
120
|
+
a.attnotnull AS column_not_null,
|
|
121
|
+
ROW_NUMBER () OVER (PARTITION BY t.oid ORDER BY a.attnum) AS column_num,
|
|
122
|
+
pg_catalog.format_type(a.atttypid, a.atttypmod) AS column_datatype,
|
|
123
|
+
(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)
|
|
124
|
+
FROM pg_catalog.pg_attrdef d
|
|
125
|
+
WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) AS column_default
|
|
126
|
+
FROM pg_catalog.pg_attribute a
|
|
127
|
+
JOIN pg_catalog.pg_class t ON a.attrelid = t.oid
|
|
128
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace
|
|
129
|
+
WHERE a.attnum > 0
|
|
130
|
+
AND NOT a.attisdropped
|
|
131
|
+
AND t.relkind IN ('r', 'v')
|
|
132
|
+
AND n.nspname = '#{curr_schema}'
|
|
133
|
+
ORDER BY column_num
|
|
134
|
+
")
|
|
135
|
+
|
|
136
|
+
for row in results do
|
|
137
|
+
schema_structure[TABLE_TYPE[row['table_type']]][row['table_name']][:columns][row['column_name']] = {
|
|
138
|
+
:column_num => row['column_num'], # column_num does not use a.attnum, since that field keeps incrementing after dropping/adding columns
|
|
139
|
+
:column_has_default => row['column_has_default'],
|
|
140
|
+
:column_default => row['column_default'].nil? ? row['column_default'] : row['column_default'].gsub(curr_schema, SCHEMA_PLACEHOLDER), # remove the schema, since sequences include it in their name
|
|
141
|
+
:column_not_null => row['column_not_null'],
|
|
142
|
+
:column_datatype => row['column_datatype']
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# get all triggers for each table
|
|
147
|
+
results = db_connection.exec("
|
|
148
|
+
SELECT n.nspname AS table_schema,
|
|
149
|
+
t.relname AS table_name,
|
|
150
|
+
t.relkind AS table_type,
|
|
151
|
+
tr.tgname AS trigger_name,
|
|
152
|
+
pg_catalog.pg_get_triggerdef(tr.oid, true) AS trigger_definition
|
|
153
|
+
FROM pg_catalog.pg_trigger tr
|
|
154
|
+
JOIN pg_catalog.pg_class t ON tr.tgrelid = t.oid
|
|
155
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace
|
|
156
|
+
WHERE t.relkind IN ('r', 'v')
|
|
157
|
+
AND (NOT tr.tgisinternal OR (tr.tgisinternal AND tr.tgenabled = 'D'))
|
|
158
|
+
AND n.nspname = '#{curr_schema}'
|
|
159
|
+
")
|
|
160
|
+
|
|
161
|
+
for row in results do
|
|
162
|
+
schema_structure[TABLE_TYPE[row['table_type']]][row['table_name']][:triggers][row['trigger_name']] = {
|
|
163
|
+
:trigger_definition => row['trigger_definition'].gsub(curr_schema, SCHEMA_PLACEHOLDER) # replace the schema with a value to later replace, to create the trigger definition on the new schema
|
|
164
|
+
}
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# get all constraints for each table
|
|
168
|
+
# TODO: this will get face some issues with VFK to public_companies (that logic is specific to CW, but might make an exception)
|
|
169
|
+
results = db_connection.exec("
|
|
170
|
+
SELECT n.nspname AS table_schema,
|
|
171
|
+
t.relname AS table_name,
|
|
172
|
+
t.relkind AS table_type,
|
|
173
|
+
con.conname AS constraint_name,
|
|
174
|
+
con.consrc AS constraint_source,
|
|
175
|
+
pg_get_constraintdef(con.oid, true) AS constraint_definition
|
|
176
|
+
FROM pg_catalog.pg_constraint con
|
|
177
|
+
JOIN pg_catalog.pg_class t ON con.conrelid = t.oid
|
|
178
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace
|
|
179
|
+
WHERE t.relkind IN ('r', 'v')
|
|
180
|
+
AND n.nspname = '#{curr_schema}'
|
|
181
|
+
")
|
|
182
|
+
|
|
183
|
+
for row in results do
|
|
184
|
+
schema_structure[TABLE_TYPE[row['table_type']]][row['table_name']][:constraints][row['constraint_name']] = {
|
|
185
|
+
:constraint_source => row['constraint_source'],
|
|
186
|
+
:constraint_definition => row['constraint_definition']
|
|
187
|
+
}
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# get all indexes for each table
|
|
191
|
+
results = db_connection.exec("
|
|
192
|
+
SELECT n.nspname AS table_schema,
|
|
193
|
+
t.relname AS table_name,
|
|
194
|
+
t.relkind AS table_type,
|
|
195
|
+
i.relname AS index_name,
|
|
196
|
+
pg_catalog.pg_get_indexdef(ix.indexrelid, 0, true) AS index_definition,
|
|
197
|
+
array_to_string(array_agg(a.attname), ', ') AS index_columns
|
|
198
|
+
FROM pg_catalog.pg_index ix
|
|
199
|
+
JOIN pg_catalog.pg_class i ON ix.indexrelid = i.oid
|
|
200
|
+
JOIN pg_catalog.pg_class t ON ix.indrelid = t.oid
|
|
201
|
+
JOIN pg_catalog.pg_attribute a ON a.attrelid = t.oid
|
|
202
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace
|
|
203
|
+
WHERE t.relkind IN ('r', 'v')
|
|
204
|
+
AND a.attnum = ANY(ix.indkey)
|
|
205
|
+
AND n.nspname = '#{curr_schema}'
|
|
206
|
+
GROUP BY 1, 2, 3, 4, 5
|
|
207
|
+
")
|
|
208
|
+
|
|
209
|
+
for row in results do
|
|
210
|
+
schema_structure[TABLE_TYPE[row['table_type']]][row['table_name']][:indexes][row['index_name']] = {
|
|
211
|
+
:index_definition => row['index_definition'].gsub(curr_schema, SCHEMA_PLACEHOLDER), # replace the schema with a value to later replace, to create the trigger definition on the new schema
|
|
212
|
+
:index_columns => row['index_columns']
|
|
213
|
+
}
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
return schema_structure
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def self.get_info_base_structure
|
|
220
|
+
return {
|
|
221
|
+
:tables => {
|
|
222
|
+
:missing => {},
|
|
223
|
+
:extra => [],
|
|
224
|
+
:mismatching => {}
|
|
225
|
+
},
|
|
226
|
+
:views => {
|
|
227
|
+
:missing => [],
|
|
228
|
+
:extra => [],
|
|
229
|
+
:mismatching => {}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def self.setup_table_info (info, table_name)
|
|
235
|
+
# create table structure if one does not exist
|
|
236
|
+
if info[:tables][:mismatching][table_name].nil?
|
|
237
|
+
info[:tables][:mismatching][table_name] = {
|
|
238
|
+
:columns => {
|
|
239
|
+
:missing => {},
|
|
240
|
+
:extra => [],
|
|
241
|
+
:mismatching => {}
|
|
242
|
+
},
|
|
243
|
+
:indexes => {
|
|
244
|
+
:missing => {},
|
|
245
|
+
:extra => [],
|
|
246
|
+
:mismatching => {}
|
|
247
|
+
},
|
|
248
|
+
:triggers => {
|
|
249
|
+
:missing => {},
|
|
250
|
+
:extra => [],
|
|
251
|
+
:mismatching => {}
|
|
252
|
+
},
|
|
253
|
+
:constraints => {
|
|
254
|
+
:missing => {},
|
|
255
|
+
:extra => [],
|
|
256
|
+
:mismatching => {}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# table comparison
|
|
264
|
+
def self.check_different_tables (left_schema, right_schema, left_info, right_info)
|
|
265
|
+
if !(keys_diff = left_schema.keys - right_schema.keys).empty?
|
|
266
|
+
left_info[:tables][:extra] += keys_diff
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
if !(keys_diff = right_schema.keys - left_schema.keys).empty?
|
|
270
|
+
keys_diff.each do |table_key|
|
|
271
|
+
left_info[:tables][:missing][table_key] = right_schema[table_key]
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# views comparison
|
|
277
|
+
def self.check_different_views (left_schema, right_schema, left_info, right_info)
|
|
278
|
+
if !(keys_diff = left_schema.keys - right_schema.keys).empty?
|
|
279
|
+
left_info[:views][:extra] += keys_diff
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
if !(keys_diff = right_schema.keys - left_schema.keys).empty?
|
|
283
|
+
left_info[:views][:missing] += keys_diff
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# column comparison
|
|
289
|
+
def self.check_different_columns (left_schema, right_schema, left_info, right_info)
|
|
290
|
+
left_schema.each do |table_key, table_value|
|
|
291
|
+
# ignore tables that only appear in one of the schemas
|
|
292
|
+
if left_info[:tables][:missing].include?(table_key) || right_info[:tables][:missing].include?(table_key)
|
|
293
|
+
_debug "Skipping table (1): #{table_key}"
|
|
294
|
+
next
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
if !(keys_diff = left_schema[table_key][:columns].keys - right_schema[table_key][:columns].keys).empty?
|
|
298
|
+
setup_table_info(left_info, table_key)
|
|
299
|
+
left_info[:tables][:mismatching][table_key][:columns][:extra] += keys_diff
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
if !(keys_diff = right_schema[table_key][:columns].keys - left_schema[table_key][:columns].keys).empty?
|
|
303
|
+
setup_table_info(left_info, table_key)
|
|
304
|
+
keys_diff.each do |column_key|
|
|
305
|
+
left_info[:tables][:mismatching][table_key][:columns][:missing][column_key] = right_schema[table_key][:columns][column_key]
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def self.check_mismatching_columns (left_schema, right_schema, left_info, right_info)
|
|
312
|
+
left_schema.each do |table_key, table_value|
|
|
313
|
+
# ignore tables that only appear in one of the schemas
|
|
314
|
+
if left_info[:tables][:missing].include?(table_key) || right_info[:tables][:missing].include?(table_key)
|
|
315
|
+
_debug "Skipping table (2): #{table_key}"
|
|
316
|
+
next
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
table_value[:columns].each do |column_key, column_value|
|
|
320
|
+
# ignore columns that only appear in one of the tables
|
|
321
|
+
if (!left_info[:tables][:mismatching][table_key].nil? && !right_info[:tables][:mismatching][table_key].nil?) && (left_info[:tables][:mismatching][table_key][:columns][:missing].include?(column_key) || right_info[:tables][:mismatching][table_key][:columns][:missing].include?(column_key))
|
|
322
|
+
_debug "Skipping column: #{column_key}"
|
|
323
|
+
next
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
if left_schema[table_key][:columns][column_key] != right_schema[table_key][:columns][column_key]
|
|
327
|
+
setup_table_info(left_info, table_key)
|
|
328
|
+
left_info[:tables][:mismatching][table_key][:columns][:mismatching][column_key] = merge_left_right_hashes(left_schema[table_key][:columns][column_key], right_schema[table_key][:columns][column_key])
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# trigger comparison
|
|
336
|
+
def self.check_different_triggers (left_schema, right_schema, left_info, right_info)
|
|
337
|
+
left_schema.each do |table_key, table_value|
|
|
338
|
+
# ignore tables that only appear in one of the schemas
|
|
339
|
+
if left_info[:tables][:missing].include?(table_key) || right_info[:tables][:missing].include?(table_key)
|
|
340
|
+
_debug "Skipping table (3): #{table_key}"
|
|
341
|
+
next
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
if !(keys_diff = left_schema[table_key][:triggers].keys - right_schema[table_key][:triggers].keys).empty?
|
|
345
|
+
setup_table_info(left_info, table_key)
|
|
346
|
+
left_info[:tables][:mismatching][table_key][:triggers][:extra] += keys_diff
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
if !(keys_diff = right_schema[table_key][:triggers].keys - left_schema[table_key][:triggers].keys).empty?
|
|
350
|
+
setup_table_info(left_info, table_key)
|
|
351
|
+
keys_diff.each do |trigger_key|
|
|
352
|
+
left_info[:tables][:mismatching][table_key][:triggers][:missing][trigger_key] = right_schema[table_key][:triggers][trigger_key]
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def self.check_mismatching_triggers (left_schema, right_schema, left_info, right_info)
|
|
359
|
+
left_schema.each do |table_key, table_value|
|
|
360
|
+
# ignore tables that only appear in one of the schemas
|
|
361
|
+
if left_info[:tables][:missing].include?(table_key) || right_info[:tables][:missing].include?(table_key)
|
|
362
|
+
_debug "Skipping table (4): #{table_key}"
|
|
363
|
+
next
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
table_value[:triggers].each do |trigger_key, trigger_value|
|
|
367
|
+
# ignore triggers that only appear in one of the tables
|
|
368
|
+
if (!left_info[:tables][:mismatching][table_key].nil? && !right_info[:tables][:mismatching][table_key].nil?) && (left_info[:tables][:mismatching][table_key][:triggers][:missing].include?(trigger_key) || right_info[:tables][:mismatching][table_key][:triggers][:missing].include?(trigger_key))
|
|
369
|
+
_debug "Skipping trigger: #{trigger_key}"
|
|
370
|
+
next
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
if left_schema[table_key][:triggers][trigger_key] != right_schema[table_key][:triggers][trigger_key]
|
|
374
|
+
setup_table_info(left_info, table_key)
|
|
375
|
+
left_info[:tables][:mismatching][table_key][:triggers][:mismatching][trigger_key] = merge_left_right_hashes(left_schema[table_key][:triggers][trigger_key], right_schema[table_key][:triggers][trigger_key])
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# constraint comparison
|
|
383
|
+
def self.check_different_constraints (left_schema, right_schema, left_info, right_info)
|
|
384
|
+
left_schema.each do |table_key, table_value|
|
|
385
|
+
# ignore tables that only appear in one of the schemas
|
|
386
|
+
if left_info[:tables][:missing].include?(table_key) || right_info[:tables][:missing].include?(table_key)
|
|
387
|
+
_debug "Skipping table (5): #{table_key}"
|
|
388
|
+
next
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
if !(keys_diff = left_schema[table_key][:constraints].keys - right_schema[table_key][:constraints].keys).empty?
|
|
392
|
+
setup_table_info(left_info, table_key)
|
|
393
|
+
left_info[:tables][:mismatching][table_key][:constraints][:extra] += keys_diff
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
if !(keys_diff = right_schema[table_key][:constraints].keys - left_schema[table_key][:constraints].keys).empty?
|
|
397
|
+
setup_table_info(left_info, table_key)
|
|
398
|
+
keys_diff.each do |constraint_key|
|
|
399
|
+
left_info[:tables][:mismatching][table_key][:constraints][:missing][constraint_key] = right_schema[table_key][:constraints][constraint_key]
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def self.check_mismatching_constraints (left_schema, right_schema, left_info, right_info)
|
|
406
|
+
left_schema.each do |table_key, table_value|
|
|
407
|
+
# ignore tables that only appear in one of the schemas
|
|
408
|
+
if left_info[:tables][:missing].include?(table_key) || right_info[:tables][:missing].include?(table_key)
|
|
409
|
+
_debug "Skipping table (6): #{table_key}"
|
|
410
|
+
next
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
table_value[:constraints].each do |constraint_key, constraint_value|
|
|
414
|
+
# ignore constraints that only appear in one of the tables
|
|
415
|
+
if (!left_info[:tables][:mismatching][table_key].nil? && !right_info[:tables][:mismatching][table_key].nil?) && (left_info[:tables][:mismatching][table_key][:constraints][:missing].include?(constraint_key) || right_info[:tables][:mismatching][table_key][:constraints][:missing].include?(constraint_key))
|
|
416
|
+
_debug "Skipping constraint: #{constraint_key}"
|
|
417
|
+
next
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
if left_schema[table_key][:constraints][constraint_key] != right_schema[table_key][:constraints][constraint_key]
|
|
421
|
+
setup_table_info(left_info, table_key)
|
|
422
|
+
left_info[:tables][:mismatching][table_key][:constraints][:mismatching][constraint_key] = merge_left_right_hashes(left_schema[table_key][:constraints][constraint_key], right_schema[table_key][:constraints][constraint_key])
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
# index comparison
|
|
430
|
+
def self.check_different_indexes (left_schema, right_schema, left_info, right_info)
|
|
431
|
+
left_schema.each do |table_key, table_value|
|
|
432
|
+
# ignore tables that only appear in one of the schemas
|
|
433
|
+
if left_info[:tables][:missing].include?(table_key) || right_info[:tables][:missing].include?(table_key)
|
|
434
|
+
_debug "Skipping table (7): #{table_key}"
|
|
435
|
+
next
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
if !(keys_diff = left_schema[table_key][:indexes].keys - right_schema[table_key][:indexes].keys).empty?
|
|
439
|
+
setup_table_info(left_info, table_key)
|
|
440
|
+
left_info[:tables][:mismatching][table_key][:indexes][:extra] += keys_diff
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
if !(keys_diff = right_schema[table_key][:indexes].keys - left_schema[table_key][:indexes].keys).empty?
|
|
444
|
+
setup_table_info(left_info, table_key)
|
|
445
|
+
keys_diff.each do |index_key|
|
|
446
|
+
left_info[:tables][:mismatching][table_key][:indexes][:missing][index_key] = right_schema[table_key][:indexes][index_key]
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def self.check_mismatching_indexes (left_schema, right_schema, left_info, right_info)
|
|
453
|
+
left_schema.each do |table_key, table_value|
|
|
454
|
+
# ignore tables that only appear in one of the schemas
|
|
455
|
+
if left_info[:tables][:missing].include?(table_key) || right_info[:tables][:missing].include?(table_key)
|
|
456
|
+
_debug "Skipping table (8): #{table_key}"
|
|
457
|
+
next
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
table_value[:indexes].each do |index_key, index_value|
|
|
461
|
+
# ignore indexes that only appear in one of the tables
|
|
462
|
+
if (!left_info[:tables][:mismatching][table_key].nil? && !right_info[:tables][:mismatching][table_key].nil?) && (left_info[:tables][:mismatching][table_key][:indexes][:missing].include?(index_key) || right_info[:tables][:mismatching][table_key][:indexes][:missing].include?(index_key))
|
|
463
|
+
_debug "Skipping index: #{index_key}"
|
|
464
|
+
next
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
if left_schema[table_key][:indexes][index_key] != right_schema[table_key][:indexes][index_key]
|
|
468
|
+
setup_table_info(left_info, table_key)
|
|
469
|
+
left_info[:tables][:mismatching][table_key][:indexes][:mismatching][index_key] = merge_left_right_hashes(left_schema[table_key][:indexes][index_key], right_schema[table_key][:indexes][index_key])
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def self.print_diff_info (info, suggestion_schema, source_schema, target_schema)
|
|
476
|
+
puts "\nComparing '#{source_schema}' to '#{target_schema}'".magenta.bold
|
|
477
|
+
|
|
478
|
+
extra_tables = {}
|
|
479
|
+
missing_tables = {}
|
|
480
|
+
mismatching_tables = {}
|
|
481
|
+
mismatching_views = {}
|
|
482
|
+
|
|
483
|
+
info[:tables][:extra].each do |table|
|
|
484
|
+
print_extra "Table '#{table}'"
|
|
485
|
+
extra_tables[table] = "DROP TABLE IF EXISTS #{suggestion_schema}.#{table};"
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
info[:tables][:missing].each do |table_key, table_value|
|
|
489
|
+
print_missing "Table '#{table_key}'"
|
|
490
|
+
missing_tables[table_key] = build_create_table_lines(table_key, table_value)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# iterate over all tables with info
|
|
494
|
+
info[:tables][:mismatching].each do |table_key, table_value|
|
|
495
|
+
print_mismatching "Table '#{table_key}'"
|
|
496
|
+
|
|
497
|
+
mismatching_tables[table_key] = {
|
|
498
|
+
:isolated_drop_commands => [],
|
|
499
|
+
:isolated_create_commands => [],
|
|
500
|
+
:alter_tables => [],
|
|
501
|
+
:warnings => []
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
# alter tables
|
|
505
|
+
table_value[:constraints][:extra].each do |constraint|
|
|
506
|
+
print_extra " Constraint '#{constraint}'"
|
|
507
|
+
mismatching_tables[table_key][:alter_tables] << "DROP CONSTRAINT IF EXISTS \"#{constraint}\""
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
table_value[:columns][:extra].each do |column|
|
|
511
|
+
print_extra " Column '#{column}'"
|
|
512
|
+
mismatching_tables[table_key][:alter_tables] << "DROP COLUMN IF EXISTS #{column}"
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
table_value[:columns][:missing].each do |column_key, column_value|
|
|
516
|
+
print_missing " Column '#{column_key}'"
|
|
517
|
+
mismatching_tables[table_key][:alter_tables] << build_add_column_line(column_key, column_value, table_key)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
table_value[:columns][:mismatching].each do |column_key, column_value|
|
|
521
|
+
print_mismatching " Column '#{column_key}'"
|
|
522
|
+
column_warnings, column_alter_tables = build_mismatching_column_lines(column_key, column_value)
|
|
523
|
+
mismatching_tables[table_key][:warnings] += column_warnings
|
|
524
|
+
mismatching_tables[table_key][:alter_tables] += column_alter_tables
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
table_value[:constraints][:missing].each do |constraint_key, constraint_value|
|
|
528
|
+
print_missing " Constraint '#{constraint_key}'"
|
|
529
|
+
mismatching_tables[table_key][:alter_tables] << build_add_constraint_line(constraint_key, constraint_value)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# isolated drop commands
|
|
533
|
+
table_value[:triggers][:extra].each do |trigger|
|
|
534
|
+
print_extra " Trigger '#{trigger}'"
|
|
535
|
+
mismatching_tables[table_key][:isolated_drop_commands] << "DROP TRIGGER IF EXISTS #{trigger} ON #{suggestion_schema}.#{table_key}"
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
table_value[:indexes][:extra].each do |index|
|
|
539
|
+
print_extra " Index '#{index}'"
|
|
540
|
+
mismatching_tables[table_key][:isolated_drop_commands] << "DROP INDEX IF EXISTS #{suggestion_schema}.#{index}"
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# isolated create commands
|
|
544
|
+
table_value[:indexes][:missing].each do |index_key, index_value|
|
|
545
|
+
print_missing " Index '#{index_key}'"
|
|
546
|
+
mismatching_tables[table_key][:isolated_create_commands] << build_add_index_line(index_key, index_value)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
table_value[:triggers][:missing].each do |trigger_key, trigger_value|
|
|
550
|
+
print_missing " Trigger '#{trigger_key}'"
|
|
551
|
+
mismatching_tables[table_key][:isolated_create_commands] << build_add_trigger_line(trigger_key, trigger_value)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# warnings
|
|
555
|
+
table_value[:triggers][:mismatching].each do |trigger_key, trigger_value|
|
|
556
|
+
print_mismatching " Trigger '#{trigger_key}'"
|
|
557
|
+
mismatching_tables[table_key][:warnings] += build_mismatching_trigger_lines(trigger_key, trigger_value)
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
table_value[:constraints][:mismatching].each do |constraint_key, constraint_value|
|
|
561
|
+
print_mismatching " Constraint '#{constraint_key}'"
|
|
562
|
+
mismatching_tables[table_key][:warnings] += build_mismatching_constraint_lines(constraint_key, constraint_value)
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
table_value[:indexes][:mismatching].each do |index_key, index_value|
|
|
566
|
+
print_mismatching " Index '#{index_key}'"
|
|
567
|
+
mismatching_tables[table_key][:warnings] += build_mismatching_index_lines(index_key, index_value)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
# iterate over all views with info
|
|
573
|
+
info[:views][:extra].each do |view_key|
|
|
574
|
+
print_extra "View '#{view_key}'"
|
|
575
|
+
mismatching_views[view_key] = "View '#{view_key.bold}' exists in '#{source_schema}' but not in the target schema. Might need to drop it"
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
info[:views][:missing].each do |view_key|
|
|
579
|
+
print_missing "View '#{view_key}'"
|
|
580
|
+
mismatching_views[view_key] = "View '#{view_key.bold}' does not exist in '#{source_schema}'. Might need to recreate it"
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
info[:views][:mismatching].each do |view_key|
|
|
584
|
+
print_mismatching "View '#{view_key}'"
|
|
585
|
+
mismatching_views[view_key] = "View '#{view_key.bold}' does not match between schemas, please recreate it"
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
command_suggestions = {
|
|
589
|
+
:extra_tables => extra_tables,
|
|
590
|
+
:missing_tables => missing_tables,
|
|
591
|
+
:mismatching_tables => mismatching_tables,
|
|
592
|
+
:mismatching_views => mismatching_views
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return command_suggestions
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def self.print_schema_correction_suggestions (schema_suggestion, suggestions)
|
|
599
|
+
|
|
600
|
+
suggestions[:extra_tables].each do |table_key, command|
|
|
601
|
+
puts "\n-- #{table_key}".white.bold
|
|
602
|
+
puts "#{command}".green.bold
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
suggestions[:missing_tables].each do |table_key, table_value|
|
|
606
|
+
puts "\n-- #{table_key}".white.bold
|
|
607
|
+
puts "CREATE TABLE IF NOT EXISTS #{schema_suggestion}.#{table_key}();".green.bold
|
|
608
|
+
print_alter_table_commands(schema_suggestion, table_key, table_value[:alter_tables])
|
|
609
|
+
table_value[:isolated_commands].each do |command|
|
|
610
|
+
puts "#{command};".green.bold
|
|
611
|
+
end
|
|
612
|
+
table_value[:warnings].each do |warning|
|
|
613
|
+
_warn "#{warning}"
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
suggestions[:mismatching_tables].each do |table_key, table_value|
|
|
618
|
+
puts "\n-- #{table_key}".white.bold
|
|
619
|
+
|
|
620
|
+
# print isolated drop commands
|
|
621
|
+
table_value[:isolated_drop_commands].each do |command|
|
|
622
|
+
puts "#{command};".green.bold
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
print_alter_table_commands(schema_suggestion, table_key, table_value[:alter_tables])
|
|
626
|
+
|
|
627
|
+
# print isolated create commands
|
|
628
|
+
table_value[:isolated_create_commands].each do |command|
|
|
629
|
+
puts "#{command};".green.bold
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# print warnings
|
|
633
|
+
table_value[:warnings].each do |command|
|
|
634
|
+
_warn "#{command}"
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
suggestions[:mismatching_views].each do |view_key, command|
|
|
639
|
+
puts "\n-- #{view_key} (View)".white.bold
|
|
640
|
+
_warn "#{command}"
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def self.print_extra (message)
|
|
645
|
+
puts "+ #{message}".green.bold
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def self.print_missing (message)
|
|
649
|
+
puts "- #{message}".red.bold
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
def self.print_mismatching (message)
|
|
653
|
+
puts "? #{message}".yellow.bold
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
# print all alter table commands together
|
|
657
|
+
def self.print_alter_table_commands (schema, table_key, commands)
|
|
658
|
+
if commands.length > 0
|
|
659
|
+
puts "ALTER TABLE #{schema}.#{table_key}".green.bold
|
|
660
|
+
commands.each_with_index do |command, index|
|
|
661
|
+
terminator = (index == commands.length - 1) ? ';' : ',';
|
|
662
|
+
puts " #{command}#{terminator}".green.bold
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# takes 2 hashes and returns a object with both of the keys remapped
|
|
668
|
+
def self.merge_left_right_hashes (left_hash, right_hash)
|
|
669
|
+
left_rehashed = remap_hash(left_hash, 'left_')
|
|
670
|
+
right_rehashed = remap_hash(right_hash, 'right_')
|
|
671
|
+
merged_hash = {}.merge(left_rehashed).merge(right_rehashed)
|
|
672
|
+
return merged_hash
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
def self.remap_hash (hash, prefix)
|
|
676
|
+
return_hash = {}
|
|
677
|
+
hash.each do |key, value|
|
|
678
|
+
new_symbol = prefix + key.to_s
|
|
679
|
+
return_hash[new_symbol.to_sym] = value
|
|
680
|
+
end
|
|
681
|
+
return return_hash
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
# functions to build certain SQL commands
|
|
685
|
+
def self.build_create_table_lines(table_key, table_value)
|
|
686
|
+
alter_tables = []
|
|
687
|
+
isolated_commands = []
|
|
688
|
+
warnings = []
|
|
689
|
+
|
|
690
|
+
# columns
|
|
691
|
+
table_value[:columns].each do |column_key, column_value|
|
|
692
|
+
alter_tables << build_add_column_line(column_key, column_value, table_key)
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# triggers
|
|
696
|
+
table_value[:triggers].each do |trigger_key, trigger_value|
|
|
697
|
+
isolated_commands << build_add_trigger_line(trigger_key, trigger_value)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# constraints
|
|
701
|
+
table_value[:constraints].each do |constraint_key, constraint_value|
|
|
702
|
+
alter_tables << build_add_constraint_line(constraint_key, constraint_value)
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
# indexes
|
|
706
|
+
table_value[:indexes].each do |index_key, index_value|
|
|
707
|
+
isolated_commands << build_add_index_line(index_key, index_value)
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# warnings
|
|
711
|
+
warnings << 'When creating a table, keep in mind the tablespace!'
|
|
712
|
+
warnings << 'This is merely a suggestion of the table structure, it\'s prefered to create the table columns inside the CREATE TABLE command'
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
:alter_tables => alter_tables,
|
|
716
|
+
:isolated_commands => isolated_commands,
|
|
717
|
+
:warnings => warnings
|
|
718
|
+
}
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def self.build_add_column_line (column_key, column_info, table_key)
|
|
722
|
+
# if the column has a default value that matches serials, suggest column as SERIAL
|
|
723
|
+
# in serials, the default value is always like "nextval('<table_name>_<col_name>_seq')"
|
|
724
|
+
if column_info[:column_default] == "nextval('#{SCHEMA_PLACEHOLDER}.#{table_key}_#{column_key}_seq'::regclass)"
|
|
725
|
+
add_column_line = "ADD COLUMN #{column_key} SERIAL"
|
|
726
|
+
else
|
|
727
|
+
data_type = column_info[:column_datatype]
|
|
728
|
+
has_default = column_info[:column_has_default] == 't' ? true : false
|
|
729
|
+
default_string = has_default ? "DEFAULT #{column_info[:column_default]}" : ''
|
|
730
|
+
nullable = column_info[:column_not_null] == 't' ? 'NOT NULL' : ''
|
|
731
|
+
add_column_line = "ADD COLUMN #{column_key} #{data_type} #{nullable} #{default_string}"
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
# replace placeholder, clear extra spaces
|
|
735
|
+
return add_column_line.gsub(SCHEMA_PLACEHOLDER, @schema_variable).gsub(/\s+/, ' ').strip
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def self.build_mismatching_column_lines (column_key, column_info)
|
|
739
|
+
warnings = []
|
|
740
|
+
alter_tables = []
|
|
741
|
+
caution_message = " Changing this property may cause problems, use with caution!".light_red # "↳" -> symbol if I decide to pass this warning in a separate line
|
|
742
|
+
|
|
743
|
+
if column_info[:left_column_num] != column_info[:right_column_num]
|
|
744
|
+
warnings << "Column '#{column_key}' is on position '#{column_info[:left_column_num]}' on current schema, but on position '#{column_info[:right_column_num]}' in the target schema"
|
|
745
|
+
end
|
|
746
|
+
if column_info[:left_column_default] != column_info[:right_column_default]
|
|
747
|
+
operation = column_info[:right_column_has_default] == 't' ? "SET DEFAULT #{column_info[:right_column_default]}".gsub(SCHEMA_PLACEHOLDER, @schema_variable) : "DROP DEFAULT"
|
|
748
|
+
warnings << "Column '#{column_key.bold}' DEFAULT value differs between schemas." + caution_message
|
|
749
|
+
alter_tables << "ALTER COLUMN #{column_key} #{operation}"
|
|
750
|
+
end
|
|
751
|
+
if column_info[:left_column_datatype] != column_info[:right_column_datatype]
|
|
752
|
+
warnings << "Column '#{column_key.bold}' TYPE differs between schemas." + caution_message
|
|
753
|
+
alter_tables << "ALTER COLUMN #{column_key} SET DATA TYPE #{column_info[:right_column_datatype]}"
|
|
754
|
+
end
|
|
755
|
+
if column_info[:left_column_not_null] != column_info[:right_column_not_null]
|
|
756
|
+
operation = column_info[:left_column_not_null] == 't' ? 'DROP' : 'SET'
|
|
757
|
+
warnings << "Column '#{column_key.bold}' NOT NULL property differs between schemas." + caution_message
|
|
758
|
+
alter_tables << "ALTER COLUMN #{column_key} #{operation} NOT NULL"
|
|
759
|
+
end
|
|
760
|
+
return warnings, alter_tables
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
def self.build_add_trigger_line (trigger_key, trigger_info)
|
|
764
|
+
trigger_def = trigger_info[:trigger_definition].gsub(SCHEMA_PLACEHOLDER, @schema_variable)
|
|
765
|
+
return trigger_def
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
def self.build_mismatching_trigger_lines (trigger_key, trigger_info)
|
|
769
|
+
warnings = []
|
|
770
|
+
if trigger_info[:left_trigger_definition] != trigger_info[:right_trigger_definition]
|
|
771
|
+
warnings << "Trigger '#{trigger_key.bold}' definition is different between schemas"
|
|
772
|
+
end
|
|
773
|
+
return warnings
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def self.build_add_constraint_line (constraint_key, constraint_info)
|
|
777
|
+
constraint_def = constraint_info[:constraint_definition].gsub(SCHEMA_PLACEHOLDER, @schema_variable)
|
|
778
|
+
return "ADD CONSTRAINT \"#{constraint_key}\" #{constraint_def}"
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
def self.build_mismatching_constraint_lines (constraint_key, constraint_info)
|
|
782
|
+
warnings = []
|
|
783
|
+
if constraint_info[:left_constraint_definition] != constraint_info[:right_constraint_definition]
|
|
784
|
+
warnings << "Constraint '#{constraint_key.bold}' definition is different between schemas"
|
|
785
|
+
end
|
|
786
|
+
return warnings
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
def self.build_add_index_line(index_key, index_info)
|
|
790
|
+
index_def = index_info[:index_definition].gsub(SCHEMA_PLACEHOLDER, @schema_variable)
|
|
791
|
+
return index_def
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
def self.build_mismatching_index_lines(index_key, index_info)
|
|
795
|
+
warnings = []
|
|
796
|
+
if index_info[:left_index_definition] != index_info[:right_index_definition]
|
|
797
|
+
warnings << "Index '#{index_key.bold}' definition is different between schemas"
|
|
798
|
+
end
|
|
799
|
+
if index_info[:left_index_columns] != index_info[:right_index_columns]
|
|
800
|
+
warnings << "Index '#{index_key.bold}' affects different columns between schemas"
|
|
801
|
+
end
|
|
802
|
+
return warnings
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
end
|