ridgepole 0.9.6 → 1.0.2
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 +4 -4
- data/.rubocop.yml +9 -1
- data/.simplecov +1 -1
- data/Appraisals +4 -0
- data/CHANGELOG.md +251 -0
- data/README.md +45 -155
- data/bin/ridgepole +83 -85
- data/docker-compose.yml +26 -18
- data/gemfiles/activerecord_7.0.gemfile +7 -0
- data/lib/ridgepole/client.rb +5 -0
- data/lib/ridgepole/delta.rb +22 -0
- data/lib/ridgepole/diff.rb +57 -3
- data/lib/ridgepole/dsl_parser/context.rb +16 -0
- data/lib/ridgepole/execute_expander.rb +4 -1
- data/lib/ridgepole/ext/abstract_adapter/partition_definition.rb +19 -0
- data/lib/ridgepole/ext/abstract_adapter/partition_options.rb +34 -0
- data/lib/ridgepole/ext/abstract_adapter/partitioning.rb +40 -0
- data/lib/ridgepole/ext/abstract_mysql_adapter/partitioning.rb +71 -0
- data/lib/ridgepole/ext/abstract_mysql_adapter/schema_creation.rb +46 -0
- data/lib/ridgepole/ext/postgresql_adapter/partitioning.rb +126 -0
- data/lib/ridgepole/ext/schema_dumper.rb +24 -0
- data/lib/ridgepole/external_sql_executer.rb +11 -13
- data/lib/ridgepole/version.rb +1 -1
- data/lib/ridgepole.rb +3 -0
- data/ridgepole.gemspec +4 -3
- metadata +19 -10
data/bin/ridgepole
CHANGED
@@ -74,106 +74,104 @@ def noop_migrate(delta, options)
|
|
74
74
|
end
|
75
75
|
|
76
76
|
ARGV.options do |opt|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
77
|
+
opt.on('-c', '--config CONF_OR_FILE') { |v| config = v }
|
78
|
+
opt.on('-E', '--env ENVIRONMENT') { |v| env = v }
|
79
|
+
opt.on('-s', '--spec-name SPEC_NAME') { |v| spec_name = v }
|
80
|
+
opt.on('-a', '--apply') { set_mode[:apply] }
|
81
|
+
opt.on('-m', '--merge') do
|
82
|
+
set_mode[:apply]
|
83
|
+
options[:merge] = true
|
84
|
+
end
|
85
|
+
opt.on('-f', '--file SCHEMAFILE') { |v| file = v }
|
86
|
+
opt.on('', '--dry-run') { options[:dry_run] = true }
|
87
|
+
opt.on('', '--table-options OPTIONS') { |v| options[:table_options] = v }
|
88
|
+
opt.on('', '--table-hash-options OPTIONS') do |v|
|
89
|
+
# NOTE: Ruby2.4 doesn't support `symbolize_names: true`
|
90
|
+
hash = YAML.safe_load(v).deep_symbolize_keys
|
91
|
+
|
92
|
+
case hash[:id]
|
93
|
+
when String
|
94
|
+
hash[:id] = hash[:id].to_sym
|
95
|
+
when Hash
|
96
|
+
hash[:id][:type] = hash[:id][:type].to_sym if hash[:id][:type]
|
85
97
|
end
|
86
|
-
opt.on('-f', '--file SCHEMAFILE') { |v| file = v }
|
87
|
-
opt.on('', '--dry-run') { options[:dry_run] = true }
|
88
|
-
opt.on('', '--table-options OPTIONS') { |v| options[:table_options] = v }
|
89
|
-
opt.on('', '--table-hash-options OPTIONS') do |v|
|
90
|
-
# NOTE: Ruby2.4 doesn't support `symbolize_names: true`
|
91
|
-
hash = YAML.safe_load(v).deep_symbolize_keys
|
92
|
-
|
93
|
-
case hash[:id]
|
94
|
-
when String
|
95
|
-
hash[:id] = hash[:id].to_sym
|
96
|
-
when Hash
|
97
|
-
hash[:id][:type] = hash[:id][:type].to_sym if hash[:id][:type]
|
98
|
-
end
|
99
98
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
99
|
+
options[:table_hash_options] = hash
|
100
|
+
end
|
101
|
+
opt.on('', '--alter-extra ALTER_SPEC') { |v| options[:alter_extra] = v }
|
102
|
+
opt.on('', '--external-script SCRIPT') { |v| options[:external_script] = v }
|
103
|
+
opt.on('', '--bulk-change') do
|
104
|
+
raise OptionParser::InvalidOption, 'Cannot use `bulk-change` in `merge`' if options[:merge]
|
106
105
|
|
107
|
-
|
108
|
-
|
106
|
+
options[:bulk_change] = true
|
107
|
+
end
|
109
108
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
109
|
+
COLUMN_TYPES.each do |column_type, column_type_alias|
|
110
|
+
opt.on('', "--default-#{column_type_alias}-limit LIMIT", Integer) do |v|
|
111
|
+
options[:"default_#{column_type}_limit"] = v
|
114
112
|
end
|
113
|
+
end
|
115
114
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
if [diff_arg1, diff_arg2].any? { |i| i.nil? || i.start_with?('-') }
|
126
|
-
puts opt.help
|
127
|
-
exit 1
|
128
|
-
end
|
115
|
+
opt.on('', '--pre-query QUERY') { |v| options[:pre_query] = v }
|
116
|
+
opt.on('', '--post-query QUERY') { |v| options[:post_query] = v }
|
117
|
+
opt.on('-e', '--export') { set_mode[:export] }
|
118
|
+
opt.on('', '--split') { split = true }
|
119
|
+
opt.on('', '--split-with-dir') { split = :with_dir }
|
120
|
+
opt.on('-d', '--diff DSL1 DSL2') do |diff_arg1|
|
121
|
+
set_mode[:diff]
|
122
|
+
diff_arg2 = ARGV.first
|
129
123
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
opt.on('', '--with-apply') { diff_with_apply = true }
|
134
|
-
opt.on('-o', '--output SCHEMAFILE') { |v| output_file = v }
|
135
|
-
opt.on('-t', '--tables TABLES', Array) { |v| options[:tables] = v }
|
136
|
-
opt.on('', '--ignore-tables REGEX_LIST', Array) { |v| options[:ignore_tables] = v.map { |i| Regexp.new(i) } }
|
137
|
-
opt.on('', '--dump-without-table-options') { options[:dump_without_table_options] = true }
|
138
|
-
opt.on('', '--dump-with-default-fk-name') { options[:dump_with_default_fk_name] = true }
|
139
|
-
opt.on('', '--index-removed-drop-column') { options[:index_removed_drop_column] = true }
|
140
|
-
opt.on('', '--skip-drop-table') { options[:skip_drop_table] = true }
|
141
|
-
opt.on('', '--mysql-change-table-options') { options[:mysql_change_table_options] = true }
|
142
|
-
opt.on('', '--mysql-change-table-comment') { options[:mysql_change_table_comment] = true }
|
143
|
-
opt.on('', '--check-relation-type DEF_PK') { |v| options[:check_relation_type] = v }
|
144
|
-
opt.on('', '--ignore-table-comment') { options[:ignore_table_comment] = true }
|
145
|
-
opt.on('', '--skip-column-comment-change') { options[:skip_column_comment_change] = true }
|
146
|
-
opt.on('', '--allow-pk-change') { options[:allow_pk_change] = true }
|
147
|
-
opt.on('', '--create-table-with-index') { options[:create_table_with_index] = true }
|
148
|
-
|
149
|
-
opt.on('', '--mysql-dump-auto-increment') do
|
150
|
-
options[:mysql_dump_auto_increment] = true
|
124
|
+
if [diff_arg1, diff_arg2].any? { |i| i.nil? || i.start_with?('-') }
|
125
|
+
puts opt.help
|
126
|
+
exit 1
|
151
127
|
end
|
152
128
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
129
|
+
ARGV.shift
|
130
|
+
diff_files = [diff_arg1, diff_arg2]
|
131
|
+
end
|
132
|
+
opt.on('', '--with-apply') { diff_with_apply = true }
|
133
|
+
opt.on('-o', '--output SCHEMAFILE') { |v| output_file = v }
|
134
|
+
opt.on('-t', '--tables TABLES', Array) { |v| options[:tables] = v }
|
135
|
+
opt.on('', '--ignore-tables REGEX_LIST', Array) { |v| options[:ignore_tables] = v.map { |i| Regexp.new(i) } }
|
136
|
+
opt.on('', '--dump-without-table-options') { options[:dump_without_table_options] = true }
|
137
|
+
opt.on('', '--dump-with-default-fk-name') { options[:dump_with_default_fk_name] = true }
|
138
|
+
opt.on('', '--index-removed-drop-column') { options[:index_removed_drop_column] = true }
|
139
|
+
opt.on('', '--drop-table') { options[:force_drop_table] = true }
|
140
|
+
opt.on('', '--mysql-change-table-options') { options[:mysql_change_table_options] = true }
|
141
|
+
opt.on('', '--mysql-change-table-comment') { options[:mysql_change_table_comment] = true }
|
142
|
+
opt.on('', '--check-relation-type DEF_PK') { |v| options[:check_relation_type] = v }
|
143
|
+
opt.on('', '--ignore-table-comment') { options[:ignore_table_comment] = true }
|
144
|
+
opt.on('', '--skip-column-comment-change') { options[:skip_column_comment_change] = true }
|
145
|
+
opt.on('', '--allow-pk-change') { options[:allow_pk_change] = true }
|
146
|
+
opt.on('', '--create-table-with-index') { options[:create_table_with_index] = true }
|
147
|
+
|
148
|
+
opt.on('', '--mysql-dump-auto-increment') do
|
149
|
+
options[:mysql_dump_auto_increment] = true
|
150
|
+
end
|
163
151
|
|
164
|
-
|
152
|
+
opt.on('-r', '--require LIBS', Array) { |v| v.each { |i| require i } }
|
153
|
+
opt.on('', '--log-file LOG_FILE') { |v| options[:log_file] = v }
|
154
|
+
opt.on('', '--verbose') { Ridgepole::Logger.verbose = true }
|
155
|
+
opt.on('', '--debug') { options[:debug] = true }
|
156
|
+
opt.on('', '--[no-]color') { |v| options[:color] = v }
|
165
157
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
rescue StandardError => e
|
171
|
-
warn("[ERROR] #{e.message}")
|
158
|
+
opt.on('-v', '--version') do
|
159
|
+
puts opt.ver
|
160
|
+
exit
|
161
|
+
end
|
172
162
|
|
173
|
-
|
163
|
+
opt.parse!
|
174
164
|
|
165
|
+
if !mode || (%i[apply export].include?(mode) && !config) || (options[:with_apply] && !config)
|
166
|
+
puts opt.help
|
175
167
|
exit 1
|
176
168
|
end
|
169
|
+
rescue StandardError => e
|
170
|
+
warn("[ERROR] #{e.message}")
|
171
|
+
|
172
|
+
puts "\t" + e.backtrace.join("\n\t") unless e.is_a?(OptionParser::ParseError)
|
173
|
+
|
174
|
+
exit 1
|
177
175
|
end
|
178
176
|
|
179
177
|
begin
|
data/docker-compose.yml
CHANGED
@@ -1,18 +1,26 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
1
|
+
version: "3.8"
|
2
|
+
services:
|
3
|
+
mysql:
|
4
|
+
image: "mysql:5.6"
|
5
|
+
ports:
|
6
|
+
- "13316:3306"
|
7
|
+
environment:
|
8
|
+
MYSQL_ROOT_PASSWORD: password
|
9
|
+
mysql57:
|
10
|
+
image: "mysql:5.7"
|
11
|
+
ports:
|
12
|
+
- "13317:3306"
|
13
|
+
environment:
|
14
|
+
MYSQL_ROOT_PASSWORD: password
|
15
|
+
mysql80:
|
16
|
+
image: "mysql:8.0"
|
17
|
+
ports:
|
18
|
+
- "13318:3306"
|
19
|
+
environment:
|
20
|
+
MYSQL_ROOT_PASSWORD: password
|
21
|
+
postgres:
|
22
|
+
image: "postgres:14"
|
23
|
+
ports:
|
24
|
+
- "15442:5432"
|
25
|
+
environment:
|
26
|
+
POSTGRES_PASSWORD: password
|
data/lib/ridgepole/client.rb
CHANGED
@@ -15,7 +15,12 @@ module Ridgepole
|
|
15
15
|
@parser = Ridgepole::DSLParser.new(@options)
|
16
16
|
@diff = Ridgepole::Diff.new(@options)
|
17
17
|
|
18
|
+
if Ridgepole::ConnectionAdapters.mysql?
|
19
|
+
require 'ridgepole/ext/abstract_mysql_adapter/partitioning'
|
20
|
+
require 'ridgepole/ext/abstract_mysql_adapter/schema_creation'
|
21
|
+
end
|
18
22
|
require 'ridgepole/ext/abstract_mysql_adapter/dump_auto_increment' if @options[:mysql_dump_auto_increment]
|
23
|
+
require 'ridgepole/ext/postgresql_adapter/partitioning' if Ridgepole::ConnectionAdapters.postgresql?
|
19
24
|
end
|
20
25
|
|
21
26
|
def dump(&block)
|
data/lib/ridgepole/delta.rb
CHANGED
@@ -310,11 +310,29 @@ execute "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name
|
|
310
310
|
append_change_table_options(table_name, comment_literal, buf)
|
311
311
|
end
|
312
312
|
|
313
|
+
def append_change_partition(table_name, delta, buf)
|
314
|
+
(delta[:add] || {}).each do |_, attrs|
|
315
|
+
buf.puts "create_partition #{table_name.inspect}, **#{attrs.inspect}"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def append_change_partition_definitions(table_name, partition_definitions, buf, _post_buf_for_fk)
|
320
|
+
(partition_definitions[:add] || []).each do |partition_name, attrs|
|
321
|
+
buf.puts "add_partition #{table_name.inspect}, name: #{partition_name.inspect}, values: #{attrs[:values].inspect}"
|
322
|
+
end
|
323
|
+
|
324
|
+
(partition_definitions[:delete] || []).each do |partition_name, _attrs|
|
325
|
+
buf.puts "remove_partition #{table_name.inspect}, name: #{partition_name.inspect}"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
313
329
|
def append_change(table_name, attrs, buf, pre_buf_for_fk, post_buf_for_fk)
|
314
330
|
definition = attrs[:definition] || {}
|
315
331
|
primary_key_definition = attrs[:primary_key_definition] || {}
|
316
332
|
indices = attrs[:indices] || {}
|
317
333
|
foreign_keys = attrs[:foreign_keys] || {}
|
334
|
+
partition = attrs[:partition] || {}
|
335
|
+
partition_definitions = attrs[:partition_definitions] || {}
|
318
336
|
table_options = attrs[:table_options]
|
319
337
|
table_charset = attrs[:table_charset]
|
320
338
|
table_collation = attrs[:table_collation]
|
@@ -335,6 +353,10 @@ execute "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name
|
|
335
353
|
|
336
354
|
append_change_table_comment(table_name, table_comment, buf) if table_comment
|
337
355
|
|
356
|
+
append_change_partition(table_name, partition, buf) unless partition.empty?
|
357
|
+
|
358
|
+
append_change_partition_definitions(table_name, partition_definitions, buf, post_buf_for_fk) unless partition_definitions.empty?
|
359
|
+
|
338
360
|
buf.puts
|
339
361
|
pre_buf_for_fk.puts
|
340
362
|
post_buf_for_fk.puts
|
data/lib/ridgepole/diff.rb
CHANGED
@@ -37,11 +37,14 @@ module Ridgepole
|
|
37
37
|
delta[:add] ||= {}
|
38
38
|
delta[:add][table_name] = to_attrs
|
39
39
|
end
|
40
|
+
delta[:change] ||= {}
|
41
|
+
delta[:change][table_name] ||= {}
|
42
|
+
scan_partition_change(table_name, from_attrs&.fetch(:partition, nil), to_attrs&.fetch(:partition, nil), delta[:change][table_name])
|
40
43
|
end
|
41
44
|
|
42
45
|
scan_relation_info(relation_info)
|
43
46
|
|
44
|
-
|
47
|
+
if !@options[:merge] && @options[:force_drop_table]
|
45
48
|
from.each do |table_name, from_attrs|
|
46
49
|
next unless target?(table_name)
|
47
50
|
|
@@ -182,8 +185,8 @@ module Ridgepole
|
|
182
185
|
to = to.except(*PRIMARY_KEY_OPTIONS)
|
183
186
|
|
184
187
|
unless from == to
|
185
|
-
@logger.
|
186
|
-
|
188
|
+
@logger.verbose_info(<<-MSG)
|
189
|
+
# Table option changes are ignored on `#{table_name}`.
|
187
190
|
from: #{from}
|
188
191
|
to: #{to}
|
189
192
|
MSG
|
@@ -419,6 +422,9 @@ module Ridgepole
|
|
419
422
|
opts[:limit] = 4_294_967_295
|
420
423
|
end
|
421
424
|
end
|
425
|
+
|
426
|
+
# Workaround for Active Record 7.0
|
427
|
+
opts.delete(:precision) if attrs[:type] == :datetime && opts[:precision].nil?
|
422
428
|
end
|
423
429
|
end
|
424
430
|
|
@@ -609,6 +615,54 @@ module Ridgepole
|
|
609
615
|
end
|
610
616
|
end
|
611
617
|
|
618
|
+
def scan_partition_change(table_name, from, to, table_delta)
|
619
|
+
from = (from || {}).dup
|
620
|
+
to = (to || {}).dup
|
621
|
+
partition_delta = {}
|
622
|
+
|
623
|
+
return if to.empty? && from.empty?
|
624
|
+
|
625
|
+
if from.empty? && Ridgepole::ConnectionAdapters.mysql?
|
626
|
+
partition_delta[:add] ||= {}
|
627
|
+
partition_delta[:add][table_name] = to
|
628
|
+
else
|
629
|
+
if from.present? && (to[:type] != from[:type] || to[:columns] != from[:columns])
|
630
|
+
@logger.warn(<<-MSG)
|
631
|
+
"[WARNING] '#{table_name}' partition is skipped because of the different partition type.
|
632
|
+
to: #{to[:type]} #{to[:columns]}
|
633
|
+
from: #{from[:type]}" #{from[:columns]}
|
634
|
+
MSG
|
635
|
+
return
|
636
|
+
end
|
637
|
+
|
638
|
+
raise "All partition is different. please check partition settings.to: #{to}, from: #{from}" if from[:partition_definitions].present? && (to[:partition_definitions] & from[:partition_definitions]).empty?
|
639
|
+
|
640
|
+
scan_partition_definition_chanage(from, to, table_delta)
|
641
|
+
end
|
642
|
+
|
643
|
+
table_delta[:partition] = partition_delta unless partition_delta.empty?
|
644
|
+
end
|
645
|
+
|
646
|
+
def scan_partition_definition_chanage(from, to, table_delta)
|
647
|
+
partition_definitions_delta = {}
|
648
|
+
attrs = { type: from[:type] || to[:type] }
|
649
|
+
|
650
|
+
from_partitions = (from[:partition_definitions] || []).index_by { |partition| partition[:name] }
|
651
|
+
to_partitions = (to[:partition_definitions] || []).index_by { |partition| partition[:name] }
|
652
|
+
|
653
|
+
(from_partitions.keys - to_partitions.keys).each do |name|
|
654
|
+
partition_definitions_delta[:delete] ||= {}
|
655
|
+
partition_definitions_delta[:delete][name] = attrs.merge(valuve: from_partitions[name][:values])
|
656
|
+
end
|
657
|
+
|
658
|
+
(to_partitions.keys - from_partitions.keys).each do |name|
|
659
|
+
partition_definitions_delta[:add] ||= {}
|
660
|
+
partition_definitions_delta[:add][name] = attrs.merge(values: to_partitions[name][:values])
|
661
|
+
end
|
662
|
+
|
663
|
+
table_delta[:partition_definitions] = partition_definitions_delta unless partition_definitions_delta.empty?
|
664
|
+
end
|
665
|
+
|
612
666
|
def check_table_existence(definition)
|
613
667
|
return unless @options[:tables]
|
614
668
|
|
@@ -91,6 +91,22 @@ module Ridgepole
|
|
91
91
|
}
|
92
92
|
end
|
93
93
|
|
94
|
+
def add_partition(table_name, type, columns, partition_definitions: [])
|
95
|
+
partition_definitions.each do |partition_definition|
|
96
|
+
values = partition_definition.fetch(:values)
|
97
|
+
raise ArgumentError unless values.is_a?(Hash)
|
98
|
+
|
99
|
+
values[:in] = Array.wrap(values[:in]) if values.key?(:in)
|
100
|
+
values[:to] = Array.wrap(values[:to]) if values.key?(:to)
|
101
|
+
values[:from] = Array.wrap(values[:from]) if values.key?(:from)
|
102
|
+
end
|
103
|
+
@__definition[table_name][:partition] = {
|
104
|
+
type: type,
|
105
|
+
columns: Array.wrap(columns),
|
106
|
+
partition_definitions: partition_definitions,
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
94
110
|
def require(file)
|
95
111
|
schemafile = %r{\A/}.match?(file) ? file : File.join(@__working_dir, file)
|
96
112
|
|
@@ -13,7 +13,10 @@ module Ridgepole
|
|
13
13
|
end
|
14
14
|
|
15
15
|
module ConnectionAdapterExt
|
16
|
-
def execute(
|
16
|
+
def execute(*args)
|
17
|
+
sql = args.fetch(0)
|
18
|
+
name = args[1]
|
19
|
+
|
17
20
|
if Ridgepole::ExecuteExpander.noop
|
18
21
|
if (callback = Ridgepole::ExecuteExpander.callback)
|
19
22
|
sql = append_alter_extra(sql)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
class PartitionDefinition
|
8
|
+
attr_reader :name, :values
|
9
|
+
|
10
|
+
def initialize(
|
11
|
+
name,
|
12
|
+
values
|
13
|
+
)
|
14
|
+
@name = name
|
15
|
+
@values = values
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
class PartitionOptions
|
8
|
+
attr_reader :table, :type, :columns, :partition_definitions
|
9
|
+
|
10
|
+
TYPES = %i[range list].freeze
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
table, type,
|
14
|
+
columns,
|
15
|
+
partition_definitions: []
|
16
|
+
)
|
17
|
+
@table = table
|
18
|
+
@type = type
|
19
|
+
@columns = Array.wrap(columns)
|
20
|
+
@partition_definitions = build_definitions(partition_definitions)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def build_definitions(definitions)
|
26
|
+
definitions.map do |definition|
|
27
|
+
next if definition.is_a?(PartitionDefinition)
|
28
|
+
|
29
|
+
PartitionDefinition.new(definition.fetch(:name), definition.fetch(:values))
|
30
|
+
end.compact
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
+
|
5
|
+
module Ridgepole
|
6
|
+
module Ext
|
7
|
+
module AbstractAdapter
|
8
|
+
module Partitioning
|
9
|
+
def partition(*)
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def partition_tables
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
|
17
|
+
# SchemaStatements
|
18
|
+
def create_partition(*)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_partition(*)
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove_partition(*)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ActiveRecord
|
35
|
+
module ConnectionAdapters
|
36
|
+
class AbstractAdapter
|
37
|
+
prepend Ridgepole::Ext::AbstractAdapter::Partitioning
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
4
|
+
|
5
|
+
module Ridgepole
|
6
|
+
module Ext
|
7
|
+
module AbstractMysqlAdapter
|
8
|
+
module Partitioning
|
9
|
+
def partition(table_name)
|
10
|
+
scope = quoted_scope(table_name)
|
11
|
+
|
12
|
+
partition_info = exec_query(<<~SQL, 'SCHEMA')
|
13
|
+
SELECT PARTITION_NAME, PARTITION_DESCRIPTION, PARTITION_METHOD, PARTITION_EXPRESSION
|
14
|
+
FROM information_schema.partitions
|
15
|
+
WHERE partition_name IS NOT NULL
|
16
|
+
AND table_schema = #{scope[:schema]}
|
17
|
+
AND table_name = #{scope[:name]}
|
18
|
+
SQL
|
19
|
+
return if partition_info.count == 0
|
20
|
+
|
21
|
+
type = case partition_info.first['PARTITION_METHOD']
|
22
|
+
when 'LIST COLUMNS'
|
23
|
+
:list
|
24
|
+
when 'RANGE COLUMNS'
|
25
|
+
:range
|
26
|
+
else
|
27
|
+
raise NotImplementedError, partition_info.first['PARTITION_METHOD'].to_s
|
28
|
+
end
|
29
|
+
columns = partition_info.first['PARTITION_EXPRESSION'].delete('`').split(',').map(&:to_sym)
|
30
|
+
|
31
|
+
partition_definitions = partition_info.map do |row|
|
32
|
+
values = case type
|
33
|
+
when :list
|
34
|
+
{ in: instance_eval("[#{row['PARTITION_DESCRIPTION'].gsub(/\(/, '[').gsub(/\)/, ']')}] # [1,2]", __FILE__, __LINE__) }
|
35
|
+
when :range
|
36
|
+
{ to: instance_eval("[#{row['PARTITION_DESCRIPTION']}] # [1,2]", __FILE__, __LINE__) }
|
37
|
+
else
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
{ name: row['PARTITION_NAME'], values: values }
|
42
|
+
end
|
43
|
+
|
44
|
+
ActiveRecord::ConnectionAdapters::PartitionOptions.new(table_name, type, columns, partition_definitions: partition_definitions)
|
45
|
+
end
|
46
|
+
|
47
|
+
# SchemaStatements
|
48
|
+
def create_partition(table_name, type:, columns:, partition_definitions:)
|
49
|
+
execute schema_creation.accept(ActiveRecord::ConnectionAdapters::PartitionOptions.new(table_name, type, columns, partition_definitions: partition_definitions))
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_partition(table_name, name:, values:)
|
53
|
+
pd = ActiveRecord::ConnectionAdapters::PartitionDefinition.new(name, values)
|
54
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ADD PARTITION (#{schema_creation.accept(pd)})"
|
55
|
+
end
|
56
|
+
|
57
|
+
def remove_partition(table_name, name:)
|
58
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP PARTITION #{name}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module ActiveRecord
|
66
|
+
module ConnectionAdapters
|
67
|
+
class AbstractMysqlAdapter < AbstractAdapter
|
68
|
+
prepend Ridgepole::Ext::AbstractMysqlAdapter::Partitioning
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/mysql/schema_creation'
|
4
|
+
|
5
|
+
module Ridgepole
|
6
|
+
module Ext
|
7
|
+
module AbstractMysqlAdapter
|
8
|
+
module SchemaCreation
|
9
|
+
def visit_PartitionOptions(o)
|
10
|
+
sqls = o.partition_definitions.map { |partition_definition| accept partition_definition }
|
11
|
+
function = case o.type
|
12
|
+
when :list
|
13
|
+
"LIST COLUMNS(#{o.columns.map { |column| quote_column_name(column) }.join(',')})"
|
14
|
+
when :range
|
15
|
+
"RANGE COLUMNS(#{o.columns.map { |column| quote_column_name(column) }.join(',')})"
|
16
|
+
else
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
"ALTER TABLE #{quote_table_name(o.table)} PARTITION BY #{function} (#{sqls.join(',')})"
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_PartitionDefinition(o)
|
23
|
+
if o.values.key?(:in)
|
24
|
+
"PARTITION #{o.name} VALUES IN (#{o.values[:in].map do |value|
|
25
|
+
value.is_a?(Array) ? "(#{value.map(&:inspect).join(',')})" : value.inspect
|
26
|
+
end.join(',')})"
|
27
|
+
elsif o.values.key?(:to)
|
28
|
+
"PARTITION #{o.name} VALUES LESS THAN (#{o.values[:to].map(&:inspect).join(',')})"
|
29
|
+
else
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ActiveRecord
|
39
|
+
module ConnectionAdapters
|
40
|
+
module MySQL
|
41
|
+
class SchemaCreation
|
42
|
+
prepend Ridgepole::Ext::AbstractMysqlAdapter::SchemaCreation
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|