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.
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
- begin
78
- opt.on('-c', '--config CONF_OR_FILE') { |v| config = v }
79
- opt.on('-E', '--env ENVIRONMENT') { |v| env = v }
80
- opt.on('-s', '--spec-name SPEC_NAME') { |v| spec_name = v }
81
- opt.on('-a', '--apply') { set_mode[:apply] }
82
- opt.on('-m', '--merge') do
83
- set_mode[:apply]
84
- options[:merge] = true
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
- options[:table_hash_options] = hash
101
- end
102
- opt.on('', '--alter-extra ALTER_SPEC') { |v| options[:alter_extra] = v }
103
- opt.on('', '--external-script SCRIPT') { |v| options[:external_script] = v }
104
- opt.on('', '--bulk-change') do
105
- raise OptionParser::InvalidOption, 'Cannot use `bulk-change` in `merge`' if options[:merge]
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
- options[:bulk_change] = true
108
- end
106
+ options[:bulk_change] = true
107
+ end
109
108
 
110
- COLUMN_TYPES.each do |column_type, column_type_alias|
111
- opt.on('', "--default-#{column_type_alias}-limit LIMIT", Integer) do |v|
112
- options[:"default_#{column_type}_limit"] = v
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
- opt.on('', '--pre-query QUERY') { |v| options[:pre_query] = v }
117
- opt.on('', '--post-query QUERY') { |v| options[:post_query] = v }
118
- opt.on('-e', '--export') { set_mode[:export] }
119
- opt.on('', '--split') { split = true }
120
- opt.on('', '--split-with-dir') { split = :with_dir }
121
- opt.on('-d', '--diff DSL1 DSL2') do |diff_arg1|
122
- set_mode[:diff]
123
- diff_arg2 = ARGV.first
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
- ARGV.shift
131
- diff_files = [diff_arg1, diff_arg2]
132
- end
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
- opt.on('-r', '--require LIBS', Array) { |v| v.each { |i| require i } }
154
- opt.on('', '--log-file LOG_FILE') { |v| options[:log_file] = v }
155
- opt.on('', '--verbose') { Ridgepole::Logger.verbose = true }
156
- opt.on('', '--debug') { options[:debug] = true }
157
- opt.on('', '--[no-]color') { |v| options[:color] = v }
158
-
159
- opt.on('-v', '--version') do
160
- puts opt.ver
161
- exit
162
- end
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
- opt.parse!
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
- if !mode || (%i[apply export].include?(mode) && !config) || (options[:with_apply] && !config)
167
- puts opt.help
168
- exit 1
169
- end
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
- puts "\t" + e.backtrace.join("\n\t") unless e.is_a?(OptionParser::ParseError)
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
- mysql:
2
- image: "mysql:5.6.38"
3
- ports:
4
- - "13316:3306"
5
- environment:
6
- MYSQL_ROOT_PASSWORD: password
7
- mysql57:
8
- image: "mysql:5.7.20"
9
- ports:
10
- - "13317:3306"
11
- environment:
12
- MYSQL_ROOT_PASSWORD: password
13
- postgres:
14
- image: "postgres:9.5"
15
- ports:
16
- - "15442:5432"
17
- environment:
18
- POSTGRES_PASSWORD: password
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
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 7.0.0"
6
+
7
+ gemspec path: "../"
@@ -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)
@@ -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
@@ -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
- unless @options[:merge] || @options[:skip_drop_table]
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.warn(<<-MSG)
186
- [WARNING] Table option changes are ignored on `#{table_name}`.
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(sql, name = nil)
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