convergence 0.0.1
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/.gitignore +3 -0
- data/.rspec +1 -0
- data/.rubocop.yml +32 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +88 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +34 -0
- data/bin/convergence +23 -0
- data/convergence-0.0.1.gem +0 -0
- data/convergence.gemspec +33 -0
- data/database.yml.example +5 -0
- data/lib/convergence.rb +24 -0
- data/lib/convergence/column.rb +36 -0
- data/lib/convergence/command.rb +56 -0
- data/lib/convergence/command/apply.rb +51 -0
- data/lib/convergence/command/diff.rb +26 -0
- data/lib/convergence/command/dryrun.rb +39 -0
- data/lib/convergence/command/export.rb +15 -0
- data/lib/convergence/config.rb +17 -0
- data/lib/convergence/database_connector.rb +25 -0
- data/lib/convergence/database_connector/mysql_connector.rb +27 -0
- data/lib/convergence/default_parameter.rb +32 -0
- data/lib/convergence/default_parameter/mysql_default_parameter.rb +159 -0
- data/lib/convergence/diff.rb +148 -0
- data/lib/convergence/dsl.rb +20 -0
- data/lib/convergence/dumper.rb +68 -0
- data/lib/convergence/dumper/mysql_schema_dumper.rb +149 -0
- data/lib/convergence/foreign_key.rb +11 -0
- data/lib/convergence/index.rb +9 -0
- data/lib/convergence/logger.rb +12 -0
- data/lib/convergence/module.rb +1 -0
- data/lib/convergence/pretty_diff.rb +55 -0
- data/lib/convergence/sql_generator.rb +2 -0
- data/lib/convergence/sql_generator/mysql_generator.rb +208 -0
- data/lib/convergence/table.rb +37 -0
- data/lib/convergence/version.rb +3 -0
- data/spec/config/spec_database.yml +6 -0
- data/spec/convergence/diff_spec.rb +242 -0
- data/spec/convergence/dsl_spec.rb +78 -0
- data/spec/convergence/dumper/mysql_schema_dumper_spec.rb +87 -0
- data/spec/convergence/dumper_spec.rb +40 -0
- data/spec/convergence/table_spec.rb +106 -0
- data/spec/fixtures/add_columns_to_paper.schema +25 -0
- data/spec/fixtures/add_table.schema +28 -0
- data/spec/fixtures/change_comment_columns_to_paper.schema +24 -0
- data/spec/fixtures/change_table_comment_to_paper.schema +24 -0
- data/spec/fixtures/drop_table.schema +15 -0
- data/spec/fixtures/remove_columns_to_paper.schema +23 -0
- data/spec/fixtures/test_db.sql +27 -0
- data/spec/integrations/command_dryrun.rb +73 -0
- data/spec/spec_helper.rb +18 -0
- metadata +268 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
3
|
+
class Convergence::Command::Apply < Convergence::Command
|
4
|
+
def validate!
|
5
|
+
fail ArgumentError.new('--config required') if @config.nil?
|
6
|
+
fail ArgumentError.new('--input required') unless @opts[:input]
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
validate!
|
11
|
+
input_tables = Convergence::DSL.parse(File.open(@opts[:input]).read)
|
12
|
+
current_tables = dumper.dump
|
13
|
+
execute_sql(input_tables, current_tables)
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute_sql(input_tables, current_tables)
|
17
|
+
sql = generate_sql(input_tables, current_tables)
|
18
|
+
unless sql.strip.empty?
|
19
|
+
sql = <<-SQL
|
20
|
+
SET FOREIGN_KEY_CHECKS=0;
|
21
|
+
#{sql}
|
22
|
+
SET FOREIGN_KEY_CHECKS=1;
|
23
|
+
SQL
|
24
|
+
end
|
25
|
+
sql.split(';').each do |q2|
|
26
|
+
q = q2.strip
|
27
|
+
unless q.empty?
|
28
|
+
begin
|
29
|
+
q = q + ';'
|
30
|
+
time = Benchmark.realtime { connector.client.query(q) }
|
31
|
+
logger.output q
|
32
|
+
logger.output " --> #{time}s"
|
33
|
+
rescue => e
|
34
|
+
logger.output 'Invalid Query Exception >>>'
|
35
|
+
logger.output q
|
36
|
+
logger.output '<<<'
|
37
|
+
throw e
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_sql(input_tables, current_tables)
|
44
|
+
current_tables_with_full_option =
|
45
|
+
Convergence::DefaultParameter.append_database_default_parameter(current_tables, database_adapter)
|
46
|
+
input_tables_with_full_option =
|
47
|
+
Convergence::DefaultParameter.append_database_default_parameter(input_tables, database_adapter)
|
48
|
+
delta = Convergence::Diff.new.diff(current_tables_with_full_option, input_tables_with_full_option)
|
49
|
+
sql_generator.generate(input_tables_with_full_option, delta)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Convergence::Command::Diff < Convergence::Command
|
2
|
+
def validate!
|
3
|
+
unless @opts[:diff].size == 2
|
4
|
+
fail ArgumentError.new('diff required two arguments')
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute
|
9
|
+
validate!
|
10
|
+
from = Convergence::DefaultParameter.remove_database_default_parameter(from_tables, database_adapter)
|
11
|
+
to = Convergence::DefaultParameter.remove_database_default_parameter(to_tables, database_adapter)
|
12
|
+
msg = Convergence::PrettyDiff.new(from, to).output
|
13
|
+
logger.output(msg)
|
14
|
+
msg
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def from_tables
|
20
|
+
Convergence::DSL.parse(File.open(@opts[:diff][0]).read)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_tables
|
24
|
+
Convergence::DSL.parse(File.open(@opts[:diff][1]).read)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Convergence::Command::Dryrun < Convergence::Command
|
2
|
+
def validate!
|
3
|
+
fail ArgumentError.new('--config required') if @config.nil?
|
4
|
+
fail ArgumentError.new('--input required') unless @opts[:input]
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute
|
8
|
+
validate!
|
9
|
+
input_tables = Convergence::DSL.parse(File.open(@opts[:input]).read)
|
10
|
+
current_tables = dumper.dump
|
11
|
+
# -- maybe it's redundant output
|
12
|
+
# output_diff(input_tables, current_tables)
|
13
|
+
output_sql(input_tables, current_tables)
|
14
|
+
end
|
15
|
+
|
16
|
+
def output_diff(input_tables, current_tables)
|
17
|
+
input_tables_without_default_parameter =
|
18
|
+
Convergence::DefaultParameter.remove_database_default_parameter(input_tables, database_adapter)
|
19
|
+
current_tables_without_default_parameter =
|
20
|
+
Convergence::DefaultParameter.remove_database_default_parameter(current_tables, database_adapter)
|
21
|
+
|
22
|
+
msg = Convergence::PrettyDiff
|
23
|
+
.new(current_tables_without_default_parameter, input_tables_without_default_parameter)
|
24
|
+
.output
|
25
|
+
logger.output(msg)
|
26
|
+
msg
|
27
|
+
end
|
28
|
+
|
29
|
+
def output_sql(input_tables, current_tables)
|
30
|
+
msg = Convergence::Command::Apply
|
31
|
+
.new(@opts, config: @config)
|
32
|
+
.generate_sql(input_tables, current_tables)
|
33
|
+
.split("\n")
|
34
|
+
.map { |v| '# ' + v }
|
35
|
+
.join("\n")
|
36
|
+
logger.output(msg)
|
37
|
+
msg
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Convergence::Command::Export < Convergence::Command
|
2
|
+
def validate!
|
3
|
+
if @config.nil?
|
4
|
+
fail ArgumentError.new('--config required')
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute
|
9
|
+
validate!
|
10
|
+
tables = Convergence::DefaultParameter.remove_database_default_parameter(dumper.dump, database_adapter)
|
11
|
+
msg = Convergence::Dumper.new.dump_dsl(tables)
|
12
|
+
logger.output(msg)
|
13
|
+
msg
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class Convergence::Config
|
5
|
+
attr_accessor :adapter, :database, :host, :port, :username, :password
|
6
|
+
|
7
|
+
def initialize(attributes)
|
8
|
+
attributes.each do |k, v|
|
9
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load(yaml_path)
|
14
|
+
setting = YAML.load(ERB.new(File.read(yaml_path)).result)
|
15
|
+
new(setting)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Convergence::DatabaseConnector
|
2
|
+
attr_reader :connector
|
3
|
+
|
4
|
+
def initialize(config)
|
5
|
+
@connector =
|
6
|
+
case config.adapter
|
7
|
+
when 'mysql'
|
8
|
+
Convergence::DatabaseConnector::MysqlConnector.new(config)
|
9
|
+
else
|
10
|
+
fail NotImplementedError.new("#{config.adapter} not supported yet")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def client
|
15
|
+
@connector.client
|
16
|
+
end
|
17
|
+
|
18
|
+
def schema_client
|
19
|
+
@connector.schema_client
|
20
|
+
end
|
21
|
+
|
22
|
+
def config
|
23
|
+
@connector.config
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Convergence::DatabaseConnector::MysqlConnector
|
2
|
+
attr_reader :config
|
3
|
+
|
4
|
+
def initialize(config)
|
5
|
+
@config = config
|
6
|
+
end
|
7
|
+
|
8
|
+
def client(database_name = @config.database)
|
9
|
+
@mysql ||= Mysql2::Client.new(
|
10
|
+
host: @config.host,
|
11
|
+
port: @config.port,
|
12
|
+
username: @config.username,
|
13
|
+
password: @config.password,
|
14
|
+
database: database_name
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def schema_client
|
19
|
+
@schema_mysql ||= Mysql2::Client.new(
|
20
|
+
host: @config.host,
|
21
|
+
port: @config.port,
|
22
|
+
username: @config.username,
|
23
|
+
password: @config.password,
|
24
|
+
database: 'information_schema'
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Convergence::DefaultParameter
|
2
|
+
def initialize(adapter)
|
3
|
+
case adapter.downcase
|
4
|
+
when 'mysql'
|
5
|
+
@parameter_klass = Convergence::DefaultParameter::MysqlDefaultParameter.new
|
6
|
+
else
|
7
|
+
fail NotImplementedError.new("unknown adapter #{config.adapter}.")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_default_parameter(table)
|
12
|
+
@parameter_klass.remove_default_parameter(table)
|
13
|
+
end
|
14
|
+
|
15
|
+
def append_default_parameter(table)
|
16
|
+
@parameter_klass.append_default_parameter(table)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.remove_database_default_parameter(tables, adapter)
|
20
|
+
values = tables.values.map do |table|
|
21
|
+
{ table.table_name => Convergence::DefaultParameter.new(adapter).remove_default_parameter(table) }
|
22
|
+
end
|
23
|
+
values.reduce { |a, e| a.merge(e) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.append_database_default_parameter(tables, adapter)
|
27
|
+
values = tables.values.map do |table|
|
28
|
+
{ table.table_name => Convergence::DefaultParameter.new(adapter).append_default_parameter(table) }
|
29
|
+
end
|
30
|
+
values.reduce { |a, e| a.merge(e) }
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
class Convergence::DefaultParameter::MysqlDefaultParameter
|
2
|
+
DEFAULT_TABLE_PARAMETERS = {
|
3
|
+
engine: 'InnoDB',
|
4
|
+
row_format: 'Compact',
|
5
|
+
default_charset: 'utf8'
|
6
|
+
}
|
7
|
+
DEFAULT_COLLATE_NAME = {
|
8
|
+
'big5' => 'big5_chinese_ci',
|
9
|
+
'dec8' => 'dec8_swedish_ci',
|
10
|
+
'cp850' => 'cp850_general_ci',
|
11
|
+
'hp8' => ' hp8_english_ci',
|
12
|
+
'koi8r' => ' koi8r_general_ci',
|
13
|
+
'latin1' => 'latin1_swedish_ci',
|
14
|
+
'latin2' => 'latin2_general_ci',
|
15
|
+
'swe7' => 'swe7_swedish_ci',
|
16
|
+
'ascii' => 'ascii_general_ci',
|
17
|
+
'ujis' => 'ujis_japanese_ci',
|
18
|
+
'sjis' => 'sjis_japanese_ci',
|
19
|
+
'hebrew' => 'hebrew_general_ci',
|
20
|
+
'tis620' => 'tis620_thai_ci',
|
21
|
+
'euckr' => 'euckr_korean_ci',
|
22
|
+
'koi8u' => 'koi8u_general_ci',
|
23
|
+
'gb2312' => 'gb2312_chinese_ci',
|
24
|
+
'greek' => ' greek_general_ci',
|
25
|
+
'cp1250' => 'cp1250_general_ci',
|
26
|
+
'gbk' => 'gbk_chinese_ci',
|
27
|
+
'latin5' => 'latin5_turkish_ci',
|
28
|
+
'armscii8' => 'armscii8_general_ci',
|
29
|
+
'utf8' => 'utf8_general_ci',
|
30
|
+
'ucs2' => 'ucs2_general_ci',
|
31
|
+
'cp866' => 'cp866_general_ci',
|
32
|
+
'keybcs2' => 'keybcs2_general_ci',
|
33
|
+
'macce' => 'macce_general_ci',
|
34
|
+
'macroman' => 'macroman_general_ci',
|
35
|
+
'cp852' => 'cp852_general_ci',
|
36
|
+
'latin7' => 'latin7_general_ci',
|
37
|
+
'utf8mb4' => 'utf8mb4_general_ci',
|
38
|
+
'cp1251' => 'cp1251_general_ci',
|
39
|
+
'utf16' => 'utf16_general_ci',
|
40
|
+
'utf16le' => 'utf16le_general_ci',
|
41
|
+
'cp1256' => 'cp1256_general_ci',
|
42
|
+
'cp1257' => 'cp1257_general_ci',
|
43
|
+
'utf32' => 'utf32_general_ci',
|
44
|
+
'binary' => 'binary',
|
45
|
+
'geostd8' => 'eostd8_general_ci',
|
46
|
+
'cp932' => 'cp932_japanese_ci',
|
47
|
+
'eucjpms' => 'ucjpms_japanese_ci'
|
48
|
+
}
|
49
|
+
DEFAULT_COLUMN_PARAMETERS = {
|
50
|
+
null: false
|
51
|
+
}
|
52
|
+
TEXT_TYPE = [:varchar, :char, :tiny_text, :text, :mediumtext, :longtext]
|
53
|
+
DEFAULT_COLUMN_TYPE_PARAMETERS = {
|
54
|
+
tinyint: {
|
55
|
+
limit: 3
|
56
|
+
},
|
57
|
+
smallint: {
|
58
|
+
limit: 5
|
59
|
+
},
|
60
|
+
mediumint: {
|
61
|
+
limit: 8
|
62
|
+
},
|
63
|
+
int: {
|
64
|
+
limit: 11
|
65
|
+
},
|
66
|
+
bigint: {
|
67
|
+
limit: 19
|
68
|
+
},
|
69
|
+
varchar: {
|
70
|
+
limit: 255
|
71
|
+
}
|
72
|
+
}
|
73
|
+
DEFAULT_INDEX_PARAMETERS = { type: 'BTREE' }
|
74
|
+
|
75
|
+
def initialize
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove_default_parameter(table)
|
79
|
+
remove_column_default_parameter(table)
|
80
|
+
remove_table_default_parameter(table)
|
81
|
+
remove_index_default_parameter(table)
|
82
|
+
table
|
83
|
+
end
|
84
|
+
|
85
|
+
def append_default_parameter(table)
|
86
|
+
append_column_default_parameter(table)
|
87
|
+
append_table_default_parameter(table)
|
88
|
+
append_index_default_parameter(table)
|
89
|
+
table
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def remove_column_default_parameter(table)
|
95
|
+
table.columns.each do |_column_name, column|
|
96
|
+
type = column.type
|
97
|
+
parameters = DEFAULT_COLUMN_PARAMETERS.merge(DEFAULT_COLUMN_TYPE_PARAMETERS[type] || {})
|
98
|
+
if TEXT_TYPE.include?(type)
|
99
|
+
character_set = table.table_options[:default_charset] || DEFAULT_TABLE_PARAMETERS[:default_charset]
|
100
|
+
parameters = parameters.merge(
|
101
|
+
character_set: character_set,
|
102
|
+
collate: table.table_options[:collate] || DEFAULT_COLLATE_NAME[character_set.downcase])
|
103
|
+
end
|
104
|
+
parameters.each do |k, v|
|
105
|
+
if !column.options[k].nil? && column.options[k].to_s.downcase == v.to_s.downcase
|
106
|
+
column.options.delete(k)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def remove_table_default_parameter(table)
|
113
|
+
table.table_options.each do |k, v|
|
114
|
+
if !DEFAULT_TABLE_PARAMETERS[k].nil? && DEFAULT_TABLE_PARAMETERS[k].downcase == v.to_s.downcase
|
115
|
+
table.table_options.delete(k)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def remove_index_default_parameter(table)
|
121
|
+
table.indexes.each do |_, va|
|
122
|
+
va.options.each do |k, v|
|
123
|
+
if !DEFAULT_INDEX_PARAMETERS[k].nil? && DEFAULT_INDEX_PARAMETERS[k].downcase == v.to_s.downcase
|
124
|
+
va.options.delete(k)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def append_column_default_parameter(table)
|
131
|
+
table.columns.each do |_column_name, column|
|
132
|
+
type = column.type
|
133
|
+
parameters = DEFAULT_COLUMN_PARAMETERS
|
134
|
+
.merge(DEFAULT_COLUMN_TYPE_PARAMETERS[type] || {})
|
135
|
+
.merge(column.options)
|
136
|
+
if TEXT_TYPE.include?(type)
|
137
|
+
character_set = table.table_options[:default_charset] || DEFAULT_TABLE_PARAMETERS[:default_charset]
|
138
|
+
parameters = parameters.merge(
|
139
|
+
character_set: character_set,
|
140
|
+
collate: table.table_options[:collate] || DEFAULT_COLLATE_NAME[character_set.downcase])
|
141
|
+
end
|
142
|
+
column.options = parameters
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def append_table_default_parameter(table)
|
147
|
+
table.table_options = DEFAULT_TABLE_PARAMETERS.merge(table.table_options)
|
148
|
+
if table.table_options[:collate].nil?
|
149
|
+
table.table_options.merge!(collate: DEFAULT_COLLATE_NAME[table.table_options[:default_charset].downcase])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def append_index_default_parameter(table)
|
154
|
+
table.indexes.each do |_column_name, column|
|
155
|
+
parameters = DEFAULT_INDEX_PARAMETERS.merge(column.options)
|
156
|
+
column.options = parameters
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'diff/lcs'
|
2
|
+
|
3
|
+
class Convergence::Diff
|
4
|
+
def diff(from_database, to_database)
|
5
|
+
delta = {}
|
6
|
+
from_database = {} if from_database.nil?
|
7
|
+
delta[:add_table] = scan_add_table(from_database, to_database)
|
8
|
+
delta[:remove_table] = scan_remove_table(from_database, to_database)
|
9
|
+
delta[:change_table] = scan_change_table(from_database, to_database)
|
10
|
+
delta
|
11
|
+
end
|
12
|
+
|
13
|
+
def diff_table(from_table, to_table)
|
14
|
+
from = from_table.dup
|
15
|
+
to = to_table.dup
|
16
|
+
delta = {}
|
17
|
+
delta[:add_column] = scan_add_column(from, to)
|
18
|
+
delta[:remove_column] = scan_remove_column(from, to)
|
19
|
+
delta[:change_column] = scan_change_column(from, to)
|
20
|
+
scan_change_order_column(from, to, delta)
|
21
|
+
delta[:remove_index] = scan_change_index(to, from)
|
22
|
+
delta[:add_index] = scan_change_index(from, to)
|
23
|
+
delta[:remove_foreign_key] = scan_change_foreign_key(to, from)
|
24
|
+
delta[:add_foreign_key] = scan_change_foreign_key(from, to)
|
25
|
+
delta[:change_table_option] = scan_change_table_option(from, to)
|
26
|
+
delta
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def scan_add_table(from, to)
|
32
|
+
to.reject { |table_name, _| from.map { |k, _| k }.include?(table_name) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def scan_remove_table(from, to)
|
36
|
+
from.reject { |table_name, _| to.map { |k, _| k }.include?(table_name) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def scan_change_table(from, to)
|
40
|
+
delta = {}
|
41
|
+
target_tables = from.map { |name, _| name } & to.map { |name, _| name }
|
42
|
+
target_tables.each do |target_table|
|
43
|
+
from_table = from.find { |name, _| name == target_table }[1]
|
44
|
+
to_table = to.find { |name, _| name == target_table }[1]
|
45
|
+
diff = diff_table(from_table, to_table)
|
46
|
+
unless diff.values.all?(&:empty?)
|
47
|
+
delta[target_table] = diff
|
48
|
+
end
|
49
|
+
end
|
50
|
+
delta
|
51
|
+
end
|
52
|
+
|
53
|
+
def scan_add_column(from, to)
|
54
|
+
to.columns.reject { |column_name, _| from.columns.keys.include?(column_name) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def scan_remove_column(from, to)
|
58
|
+
from.columns.reject { |column_name, _| to.columns.keys.include?(column_name) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def scan_change_column(from, to)
|
62
|
+
change_columns = from
|
63
|
+
.columns
|
64
|
+
.map do |column_name, from_column|
|
65
|
+
to_column = to.columns[column_name]
|
66
|
+
if to_column
|
67
|
+
to_column_option_with_type = (from_column.options.map { |k, _v| { k => nil } }.reduce { |a, e| a.merge(e) } || {})
|
68
|
+
.merge(to_column.options)
|
69
|
+
.merge(type: to_column.type)
|
70
|
+
.map { |k, v| [k, v.to_s.downcase] }
|
71
|
+
.to_a
|
72
|
+
from_column_option_with_type = from_column
|
73
|
+
.options
|
74
|
+
.merge(type: from_column.type)
|
75
|
+
.map { |k, v| [k, v.to_s.downcase] }
|
76
|
+
.to_a
|
77
|
+
{ column_name => Hash[(to_column_option_with_type - from_column_option_with_type)] }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
change_columns
|
81
|
+
.compact
|
82
|
+
.reduce { |a, e| a.merge(e) }
|
83
|
+
.reject { |_k, v| v.empty? }
|
84
|
+
end
|
85
|
+
|
86
|
+
def scan_change_order_column(from, to, delta)
|
87
|
+
from_columns = from.columns.keys
|
88
|
+
to_columns = to.columns.keys
|
89
|
+
order_changed_columns = Diff::LCS.diff(from_columns, to_columns)
|
90
|
+
.flatten
|
91
|
+
.select(&:adding?)
|
92
|
+
.map(&:element)
|
93
|
+
order_changed_columns.each do |column|
|
94
|
+
before_column_index = to_columns.index { |v| v == column } - 1
|
95
|
+
before_column = if before_column_index < 0
|
96
|
+
nil
|
97
|
+
else
|
98
|
+
to_columns[before_column_index]
|
99
|
+
end
|
100
|
+
if delta[:add_column][column]
|
101
|
+
delta[:add_column][column].options.merge!(after: before_column)
|
102
|
+
else
|
103
|
+
delta[:change_column][column] = {} if delta[:change_column][column].nil?
|
104
|
+
delta[:change_column][column].merge!(after: before_column)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def scan_change_index(from, to)
|
110
|
+
delta = {}
|
111
|
+
to.indexes.each do |name, index|
|
112
|
+
candidate_index = from.indexes.find { |from_name, _| from_name == name }
|
113
|
+
if candidate_index.nil? || candidate_index[1].options != index.options
|
114
|
+
delta[name] = index
|
115
|
+
end
|
116
|
+
end
|
117
|
+
delta
|
118
|
+
end
|
119
|
+
|
120
|
+
def scan_change_foreign_key(from, to)
|
121
|
+
delta = {}
|
122
|
+
to.foreign_keys.each do |name, fk|
|
123
|
+
candidate_foreign_keys = from.foreign_keys.find { |from_name, _| from_name == name }
|
124
|
+
target_fk = candidate_foreign_keys[1] rescue nil
|
125
|
+
if candidate_foreign_keys.nil?
|
126
|
+
delta[name] = fk
|
127
|
+
elsif !target_fk.nil?
|
128
|
+
if target_fk.from_columns != fk.from_columns ||
|
129
|
+
target_fk.key_name != fk.key_name ||
|
130
|
+
target_fk.options != fk.options ||
|
131
|
+
target_fk.to_columns != fk.to_columns ||
|
132
|
+
target_fk.to_table != fk.to_table
|
133
|
+
delta[name] = fk
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
delta
|
138
|
+
end
|
139
|
+
|
140
|
+
def scan_change_table_option(from, to)
|
141
|
+
change_options = (from.table_options.map { |k, _v| { k => nil } }.reduce { |a, e| a.merge(e) } || {})
|
142
|
+
.merge(to.table_options)
|
143
|
+
.reject do |k, v|
|
144
|
+
!from.table_options[k].nil? && from.table_options[k].to_s.downcase == v.to_s.downcase
|
145
|
+
end
|
146
|
+
Hash[change_options]
|
147
|
+
end
|
148
|
+
end
|