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,20 @@
|
|
1
|
+
class Convergence::DSL
|
2
|
+
attr_accessor :tables
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@tables = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_table(table_name, options = {}, &block)
|
9
|
+
table = Convergence::Table.new(table_name, options)
|
10
|
+
block.call(table)
|
11
|
+
@tables[table_name.to_s] = table
|
12
|
+
table
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse(code)
|
16
|
+
parser = new
|
17
|
+
parser.instance_eval(code)
|
18
|
+
parser.tables
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class Convergence::Dumper
|
2
|
+
def dump_dsl(tables)
|
3
|
+
tables.map do |_, table|
|
4
|
+
dump_table_dsl(table)
|
5
|
+
end.join("\n\n")
|
6
|
+
end
|
7
|
+
|
8
|
+
def dump_table_dsl(table)
|
9
|
+
table_argument = ["\"#{table.table_name}\""]
|
10
|
+
table_argument << table.table_options.map { |k, v| key_value_text(k, v) }
|
11
|
+
dsl = "create_table #{table_argument.flatten.join(', ')} do |t|\n"
|
12
|
+
dsl += " #{table.columns.map { |_, column| dump_column(column) }.join("\n ")}"
|
13
|
+
dsl += "\n" if !table.indexes.empty? || !table.foreign_keys.empty?
|
14
|
+
dsl += "\n"
|
15
|
+
unless table.indexes.empty?
|
16
|
+
dsl += " #{table.indexes.map { |_, index| dump_index(index) }.join("\n ")}"
|
17
|
+
dsl += "\n"
|
18
|
+
end
|
19
|
+
unless table.foreign_keys.empty?
|
20
|
+
dsl += " #{table.foreign_keys.map { |_, key| dump_foreign_key(key) }.join("\n ")}"
|
21
|
+
dsl += "\n"
|
22
|
+
end
|
23
|
+
dsl += 'end'
|
24
|
+
dsl
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def dump_column(column)
|
30
|
+
argument = [%("#{column.column_name}")]
|
31
|
+
argument << column.options.map { |k, v| key_value_text(k, v) }
|
32
|
+
"t.#{column.type} #{argument.flatten.join(', ')}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def dump_index(index)
|
36
|
+
columns = single_or_multiple_text(index.index_columns)
|
37
|
+
argument = [columns]
|
38
|
+
argument << index.options.map { |k, v| key_value_text(k, v) }
|
39
|
+
"t.index #{argument.flatten.join(', ')}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def dump_foreign_key(foreign_key)
|
43
|
+
columns = single_or_multiple_text(foreign_key.from_columns)
|
44
|
+
argument = [columns]
|
45
|
+
argument << [key_value_text('reference', foreign_key.to_table)]
|
46
|
+
argument << ["reference_column: #{single_or_multiple_text(foreign_key.to_columns)}"]
|
47
|
+
argument << foreign_key.options.map { |k, v| key_value_text(k, v) }
|
48
|
+
"t.foreign_key #{argument.flatten.join(', ')}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def single_or_multiple_text(values)
|
52
|
+
values_array = [values].flatten
|
53
|
+
if values_array.size == 1
|
54
|
+
%("#{values_array.first}")
|
55
|
+
else
|
56
|
+
%(#{values})
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def key_value_text(k, v)
|
61
|
+
value = if v.to_s == 'true' || v.to_s == 'false' || v =~ /^\d+$/
|
62
|
+
v
|
63
|
+
else
|
64
|
+
%(#{v.inspect})
|
65
|
+
end
|
66
|
+
"#{k}: #{value}"
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
class Convergence::Dumper::MysqlSchemaDumper
|
3
|
+
def initialize(connector)
|
4
|
+
@connector = connector
|
5
|
+
@target_database = connector.config.database
|
6
|
+
@tables = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def dump
|
10
|
+
table_definitions = select_table_definitions(@target_database)
|
11
|
+
column_definitions = select_column_definitions(@target_database).group_by { |r| r['TABLE_NAME'] }
|
12
|
+
index_definitions = select_index_definitions(@target_database).group_by { |r| r['TABLE_NAME'] }
|
13
|
+
table_definitions.map { |r| r['TABLE_NAME'] }.each do |table_name|
|
14
|
+
table = Convergence::Table.new(table_name)
|
15
|
+
parse_table_options(table, table_definitions.find { |r| r['TABLE_NAME'] == table_name })
|
16
|
+
parse_columns(table, column_definitions[table_name])
|
17
|
+
parse_indexes(table, index_definitions[table_name])
|
18
|
+
@tables[table_name] = table
|
19
|
+
end
|
20
|
+
@tables
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def mysql
|
26
|
+
@connector.schema_client
|
27
|
+
end
|
28
|
+
|
29
|
+
def select_table_definitions(database_name)
|
30
|
+
mysql.query("
|
31
|
+
SELECT
|
32
|
+
*
|
33
|
+
FROM
|
34
|
+
TABLES
|
35
|
+
INNER JOIN
|
36
|
+
COLLATION_CHARACTER_SET_APPLICABILITY CCSA
|
37
|
+
ON
|
38
|
+
TABLES.TABLE_COLLATION = CCSA.COLLATION_NAME
|
39
|
+
WHERE
|
40
|
+
TABLE_SCHEMA = '#{mysql.escape(database_name)}'
|
41
|
+
ORDER BY
|
42
|
+
TABLE_NAME
|
43
|
+
")
|
44
|
+
end
|
45
|
+
|
46
|
+
def select_column_definitions(database_name)
|
47
|
+
mysql.query("
|
48
|
+
SELECT * FROM COLUMNS
|
49
|
+
WHERE TABLE_SCHEMA = '#{mysql.escape(database_name)}'
|
50
|
+
ORDER BY TABLE_NAME, ORDINAL_POSITION
|
51
|
+
")
|
52
|
+
end
|
53
|
+
|
54
|
+
def select_index_definitions(database_name)
|
55
|
+
mysql.query("
|
56
|
+
SELECT
|
57
|
+
DISTINCT S.TABLE_NAME, S.COLUMN_NAME, S.NON_UNIQUE, S.INDEX_NAME, S.SEQ_IN_INDEX, S.INDEX_TYPE,
|
58
|
+
IF(TC.CONSTRAINT_TYPE IS NULL, 'INDEX', TC.CONSTRAINT_TYPE) CONSTRAINT_TYPE,
|
59
|
+
KCU.REFERENCED_TABLE_NAME, KCU.REFERENCED_COLUMN_NAME
|
60
|
+
FROM
|
61
|
+
STATISTICS S
|
62
|
+
LEFT OUTER JOIN
|
63
|
+
TABLE_CONSTRAINTS TC
|
64
|
+
ON
|
65
|
+
TC.TABLE_SCHEMA = S.TABLE_SCHEMA
|
66
|
+
AND TC.TABLE_NAME = S.TABLE_NAME
|
67
|
+
AND TC.CONSTRAINT_NAME = S.INDEX_NAME
|
68
|
+
LEFT OUTER JOIN
|
69
|
+
KEY_COLUMN_USAGE KCU
|
70
|
+
ON
|
71
|
+
KCU.CONSTRAINT_SCHEMA = S.TABLE_SCHEMA
|
72
|
+
AND KCU.TABLE_NAME = S.TABLE_NAME
|
73
|
+
AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
74
|
+
WHERE
|
75
|
+
S.TABLE_SCHEMA = '#{mysql.escape(database_name)}'
|
76
|
+
ORDER BY
|
77
|
+
S.TABLE_NAME, S.INDEX_NAME, S.SEQ_IN_INDEX
|
78
|
+
")
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_table_options(table, table_option)
|
82
|
+
option = {}
|
83
|
+
option.merge!(engine: table_option['ENGINE'])
|
84
|
+
row_format = table_option['CREATE_OPTIONS'].scan(/=(.*)/).flatten[0] || table_option['ROW_FORMAT']
|
85
|
+
option.merge!(row_format: row_format)
|
86
|
+
option.merge!(default_charset: table_option['CHARACTER_SET_NAME'])
|
87
|
+
option.merge!(collate: table_option['TABLE_COLLATION'])
|
88
|
+
option.merge!(comment: table_option['TABLE_COMMENT'])
|
89
|
+
table.table_options = option
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_columns(table, columns)
|
93
|
+
columns.each do |column|
|
94
|
+
data_type, column_name, options = parse_column(column)
|
95
|
+
table.send(data_type, column_name, options)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_column(column)
|
100
|
+
data_type = column['DATA_TYPE']
|
101
|
+
column_name = column['COLUMN_NAME']
|
102
|
+
options = { null: column['IS_NULLABLE'] == 'YES' ? true : false }
|
103
|
+
options.merge!(default: column['COLUMN_DEFAULT']) unless column['COLUMN_DEFAULT'].nil?
|
104
|
+
options.merge!(character_set: column['CHARACTER_SET_NAME']) unless column['CHARACTER_SET_NAME'].nil?
|
105
|
+
options.merge!(collate: column['COLLATION_NAME']) unless column['COLLATION_NAME'].nil?
|
106
|
+
column_type = column['COLUMN_TYPE']
|
107
|
+
if data_type == 'enum' || data_type == 'set'
|
108
|
+
# TODO: implement
|
109
|
+
elsif data_type == 'decimal'
|
110
|
+
precision, scale = column_type.scan(/\d+/)
|
111
|
+
options.merge!(precision: precision, scale: scale)
|
112
|
+
else
|
113
|
+
limit = column_type.scan(/\d+/)[0]
|
114
|
+
options.merge!(limit: limit) unless limit.nil?
|
115
|
+
end
|
116
|
+
options.merge!(extra: column['EXTRA']) unless column['EXTRA'].empty?
|
117
|
+
options.merge!(comment: column['COLUMN_COMMENT']) unless column['COLUMN_COMMENT'].empty?
|
118
|
+
[data_type, column_name, options]
|
119
|
+
end
|
120
|
+
|
121
|
+
def parse_indexes(table, table_indexes)
|
122
|
+
table_indexes.group_by { |r| r['INDEX_NAME'] }.each do |index_name, indexes|
|
123
|
+
type = indexes.first['CONSTRAINT_TYPE']
|
124
|
+
columns = indexes.map { |v| v['COLUMN_NAME'] }
|
125
|
+
case type
|
126
|
+
when 'PRIMARY KEY'
|
127
|
+
indexes.map { |r| r['COLUMN_NAME'] }.each do |column|
|
128
|
+
options = { primary_key: true }.merge(table.columns[column].options)
|
129
|
+
table.columns[column].options = options
|
130
|
+
end
|
131
|
+
when 'INDEX', 'UNIQUE'
|
132
|
+
options = { name: index_name, type: indexes.first['INDEX_TYPE'] }
|
133
|
+
options.merge!(unique: true) if type == 'UNIQUE'
|
134
|
+
table.index(columns, options)
|
135
|
+
when 'FOREIGN KEY'
|
136
|
+
to_table = indexes.first['REFERENCED_TABLE_NAME']
|
137
|
+
to_columns = indexes.map { |v| v['REFERENCED_COLUMN_NAME'] }
|
138
|
+
options = {
|
139
|
+
reference: to_table,
|
140
|
+
reference_column: to_columns,
|
141
|
+
name: index_name
|
142
|
+
}
|
143
|
+
table.foreign_key(columns, options)
|
144
|
+
else
|
145
|
+
fail NotImplementedError.new('Unknown index type')
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Convergence::ForeignKey
|
2
|
+
attr_accessor :key_name, :from_columns, :to_table, :to_columns, :options
|
3
|
+
|
4
|
+
def initialize(key_name, from_columns, to_table, to_columns, options)
|
5
|
+
@key_name = key_name
|
6
|
+
@from_columns = [from_columns].flatten.map(&:to_s)
|
7
|
+
@to_table = to_table
|
8
|
+
@to_columns = [to_columns].flatten.map(&:to_s)
|
9
|
+
@options = { name: @key_name }.merge(options)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Convergence::Index
|
2
|
+
attr_accessor :index_name, :index_columns, :options
|
3
|
+
|
4
|
+
def initialize(index_name, index_columns, options)
|
5
|
+
@index_name = index_name
|
6
|
+
@index_columns = [index_columns].flatten.map(&:to_s)
|
7
|
+
@options = { name: @index_name }.merge(options)
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
module Convergence; end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'diffy'
|
2
|
+
|
3
|
+
class Convergence::PrettyDiff
|
4
|
+
def initialize(from_tables, to_tables)
|
5
|
+
@from_tables = from_tables
|
6
|
+
@to_tables = to_tables
|
7
|
+
end
|
8
|
+
|
9
|
+
def output
|
10
|
+
diff = Convergence::Diff.new.diff(@from_tables, @to_tables)
|
11
|
+
add_tables = diff[:add_table].keys
|
12
|
+
remove_tables = diff[:remove_table].keys
|
13
|
+
change_tables = diff[:change_table].keys
|
14
|
+
|
15
|
+
results = ''
|
16
|
+
add_tables.each do |table_name|
|
17
|
+
results += diff_add_table(table_name)
|
18
|
+
results += "\n\n"
|
19
|
+
end
|
20
|
+
remove_tables.each do |table_name|
|
21
|
+
results += diff_remove_table(table_name)
|
22
|
+
results += "\n\n"
|
23
|
+
end
|
24
|
+
change_tables.each do |table_name|
|
25
|
+
results += diff_change_table(table_name)
|
26
|
+
end
|
27
|
+
results
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def diff_add_table(table_name)
|
33
|
+
Convergence::Dumper
|
34
|
+
.new
|
35
|
+
.dump_table_dsl(@to_tables[table_name])
|
36
|
+
.split("\n")
|
37
|
+
.map { |v| "+ #{v}" }
|
38
|
+
.join("\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
def diff_remove_table(table_name)
|
42
|
+
Convergence::Dumper
|
43
|
+
.new
|
44
|
+
.dump_table_dsl(@from_tables[table_name])
|
45
|
+
.split("\n")
|
46
|
+
.map { |v| "- #{v}" }
|
47
|
+
.join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def diff_change_table(table_name)
|
51
|
+
from = Convergence::Dumper.new.dump_table_dsl(@from_tables[table_name]) + "\n"
|
52
|
+
to = Convergence::Dumper.new.dump_table_dsl(@to_tables[table_name]) + "\n"
|
53
|
+
Diffy::Diff.new(from, to).to_s
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
class SQLGenerator::MysqlGenerator < SQLGenerator
|
2
|
+
OPTION_MAPPING = {
|
3
|
+
engine: 'ENGINE',
|
4
|
+
row_format: 'ROW_FORMAT',
|
5
|
+
default_charset: 'DEFAULT CHARACTER SET',
|
6
|
+
collate: 'COLLATE',
|
7
|
+
comment: 'COMMENT'
|
8
|
+
}
|
9
|
+
QUOTE_OPTION = [:comment]
|
10
|
+
|
11
|
+
def generate(to_table, delta)
|
12
|
+
sqls = []
|
13
|
+
sqls << change_table_sql(to_table, delta)
|
14
|
+
sqls << ['']
|
15
|
+
sqls << create_table_sqls(delta)
|
16
|
+
sqls << drop_table_sqls(delta)
|
17
|
+
sqls.join("\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# FIXME: multiple pk change not supported yet
|
23
|
+
def change_table_sql(to_table, delta)
|
24
|
+
change_table = delta[:change_table]
|
25
|
+
results = []
|
26
|
+
change_table.each do |table_name, table_delta|
|
27
|
+
table_delta[:remove_column].each do |_column_name, column|
|
28
|
+
results << alter_remove_column_sql(table_name, column)
|
29
|
+
end
|
30
|
+
table_delta[:add_column].each do |_column_name, column|
|
31
|
+
results << alter_add_column_sql(table_name, column)
|
32
|
+
end
|
33
|
+
table_delta[:change_column].each do |column_name, column|
|
34
|
+
results << alter_change_column_sql(table_name, column_name, column, to_table)
|
35
|
+
end
|
36
|
+
table_delta[:remove_index].each do |index_name, _index|
|
37
|
+
results << alter_remove_index_sql(table_name, index_name)
|
38
|
+
end
|
39
|
+
table_delta[:add_index].each do |_index_name, index|
|
40
|
+
results << alter_add_index_sql(table_name, index)
|
41
|
+
end
|
42
|
+
table_delta[:remove_foreign_key].each do |index_name, _foreign_key|
|
43
|
+
results << alter_remove_foreign_key_sql(table_name, index_name)
|
44
|
+
end
|
45
|
+
table_delta[:add_foreign_key].each do |_index_name, foreign_key|
|
46
|
+
results << alter_add_foreign_key_sql(table_name, foreign_key)
|
47
|
+
end
|
48
|
+
unless table_delta[:change_table_option].empty?
|
49
|
+
results << alter_change_table_sql(table_name, table_delta[:change_table_option])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
results
|
53
|
+
end
|
54
|
+
|
55
|
+
def alter_add_column_sql(table_name, column)
|
56
|
+
%(ALTER TABLE `#{table_name}` ADD COLUMN #{create_column_sql(column, output_primary_key: true)};)
|
57
|
+
end
|
58
|
+
|
59
|
+
def alter_remove_column_sql(table_name, column)
|
60
|
+
%(ALTER TABLE `#{table_name}` DROP COLUMN `#{column.column_name}`;)
|
61
|
+
end
|
62
|
+
|
63
|
+
def alter_change_column_sql(table_name, column_name, change_column_option, to_table)
|
64
|
+
column = to_table[table_name].columns[column_name]
|
65
|
+
column.options.merge!(after: change_column_option[:after]) unless change_column_option[:after].nil?
|
66
|
+
%(ALTER TABLE `#{table_name}` MODIFY COLUMN #{create_column_sql(column, output_primary_key: true)};)
|
67
|
+
end
|
68
|
+
|
69
|
+
def alter_change_table_sql(table_name, change_table_option)
|
70
|
+
sql = "ALTER TABLE `#{table_name}`"
|
71
|
+
change_table_option.each do |key, value|
|
72
|
+
if QUOTE_OPTION.include?(key)
|
73
|
+
sql += " #{OPTION_MAPPING[key]}='#{value}'"
|
74
|
+
else
|
75
|
+
sql += " #{OPTION_MAPPING[key]}=#{value}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
sql += ';'
|
79
|
+
sql
|
80
|
+
end
|
81
|
+
|
82
|
+
def alter_remove_index_sql(table_name, index_name)
|
83
|
+
%(DROP INDEX `#{index_name}` ON `#{table_name}`;)
|
84
|
+
end
|
85
|
+
|
86
|
+
def alter_add_index_sql(table_name, index)
|
87
|
+
sql = 'CREATE'
|
88
|
+
sql += ' UNIQUE' if index.options[:unique]
|
89
|
+
sql += " INDEX `#{index.index_name}` ON `#{table_name}`(#{index.index_columns.join(',')});"
|
90
|
+
sql
|
91
|
+
end
|
92
|
+
|
93
|
+
def alter_remove_foreign_key_sql(table_name, index_name)
|
94
|
+
sql = %(ALTER TABLE `#{table_name}` DROP FOREIGN KEY `#{index_name}`;\n)
|
95
|
+
sql += alter_remove_index_sql(table_name, index_name)
|
96
|
+
sql
|
97
|
+
end
|
98
|
+
|
99
|
+
def alter_add_foreign_key_sql(table_name, foreign_key)
|
100
|
+
sql = %(ALTER TABLE `#{table_name}` ADD CONSTRAINT `#{foreign_key.key_name}` FOREIGN KEY )
|
101
|
+
sql += "(#{[foreign_key.from_columns].join(',')}) REFERENCES `#{foreign_key.to_table}`"
|
102
|
+
sql += "(#{[foreign_key.to_columns].join(',')});"
|
103
|
+
sql
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_table_sqls(delta)
|
107
|
+
delta[:add_table].map do |table_name, table|
|
108
|
+
column_sql = (create_table_column_sql(table) << create_table_index_sql(table))
|
109
|
+
.flatten
|
110
|
+
.reject(&:empty?)
|
111
|
+
.join(",\n ")
|
112
|
+
<<-SQL
|
113
|
+
CREATE TABLE `#{table_name}` (
|
114
|
+
#{column_sql}
|
115
|
+
) #{create_table_option_sql(table)};
|
116
|
+
SQL
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def drop_table_sqls(delta)
|
121
|
+
delta[:remove_table].map do |table_name, _|
|
122
|
+
<<-SQL
|
123
|
+
DROP TABLE `#{table_name}`;
|
124
|
+
SQL
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def create_table_column_sql(table)
|
129
|
+
table.columns.values.map do |column|
|
130
|
+
create_column_sql(column)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def create_column_sql(column, output_primary_key: false)
|
135
|
+
sql = "`#{column.column_name}`"
|
136
|
+
sql += " #{column.type}"
|
137
|
+
sql += "(#{column.options[:limit]})" unless column.options[:limit].nil?
|
138
|
+
if column.options[:precision] && column.options[:scale]
|
139
|
+
sql += "(#{column.options[:precision]}, #{column.options[:scale]})"
|
140
|
+
end
|
141
|
+
if column.options[:character_set]
|
142
|
+
sql += " CHARACTER SET #{column.options[:character_set]}"
|
143
|
+
end
|
144
|
+
if column.options[:collate]
|
145
|
+
sql += " COLLATE #{column.options[:collate]}"
|
146
|
+
end
|
147
|
+
if column.options[:null]
|
148
|
+
sql += ' DEFAULT NULL' unless column.options[:default]
|
149
|
+
else
|
150
|
+
sql += ' NOT NULL'
|
151
|
+
end
|
152
|
+
if column.options[:primary_key] && output_primary_key
|
153
|
+
sql += ' PRIMARY KEY'
|
154
|
+
end
|
155
|
+
if column.options[:default]
|
156
|
+
sql += " DEFAULT '#{column.options[:default]}'"
|
157
|
+
end
|
158
|
+
if column.options[:comment]
|
159
|
+
sql += " COMMENT '#{column.options[:comment]}'"
|
160
|
+
end
|
161
|
+
if column.options[:extra]
|
162
|
+
sql += " #{column.options[:extra].upcase}"
|
163
|
+
end
|
164
|
+
if column.options.keys.include?(:after)
|
165
|
+
if column.options[:after].nil?
|
166
|
+
sql += ' FIRST'
|
167
|
+
else
|
168
|
+
sql += " AFTER `#{column.options[:after]}`"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
sql
|
172
|
+
end
|
173
|
+
|
174
|
+
def create_table_index_sql(table)
|
175
|
+
pkeys = table.columns.select { |_k, v| v.options[:primary_key] }
|
176
|
+
unique_keys = table.indexes.values.select { |v| v.options[:unique] }
|
177
|
+
index_keys = table.indexes.values.reject { |v| v.options[:unique] }
|
178
|
+
foreign_keys = table.foreign_keys.values
|
179
|
+
results = []
|
180
|
+
unless pkeys.empty?
|
181
|
+
results << %(PRIMARY KEY (#{pkeys.keys.map { |v| "`#{v}`" }.join(',')}))
|
182
|
+
end
|
183
|
+
results << unique_keys.map do |uk|
|
184
|
+
%(UNIQUE KEY `#{uk.index_name}` (#{uk.index_columns.map { |v| "`#{v}`" }.join(',')}))
|
185
|
+
end
|
186
|
+
results << index_keys.map do |ik|
|
187
|
+
%(KEY `#{ik.index_name}` (#{ik.index_columns.map { |v| "`#{v}`" }.join(',')}))
|
188
|
+
end
|
189
|
+
results << foreign_keys.map do |fk|
|
190
|
+
sql = %(CONSTRAINT `#{fk.key_name}` FOREIGN KEY)
|
191
|
+
sql += %( (#{[fk.from_columns].flatten.map { |v| "`#{v}`" }.join(',')}))
|
192
|
+
sql += %( REFERENCES `#{fk.to_table}` (#{[fk.to_columns].flatten.map { |v| "`#{v}`" }.join(',')}))
|
193
|
+
sql
|
194
|
+
end
|
195
|
+
results
|
196
|
+
end
|
197
|
+
|
198
|
+
def create_table_option_sql(table)
|
199
|
+
table.table_options.map do |k, v|
|
200
|
+
key = OPTION_MAPPING[k] || k.to_s.upcase
|
201
|
+
if QUOTE_OPTION.include?(k)
|
202
|
+
"#{key}=\"#{v}\""
|
203
|
+
else
|
204
|
+
"#{key}=#{v}"
|
205
|
+
end
|
206
|
+
end.join(' ')
|
207
|
+
end
|
208
|
+
end
|