ridgepole 1.0.1 → 1.0.2.beta
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 +8 -0
- data/CHANGELOG.md +5 -0
- data/README.md +25 -0
- data/lib/ridgepole/client.rb +5 -0
- data/lib/ridgepole/delta.rb +22 -0
- data/lib/ridgepole/diff.rb +53 -2
- data/lib/ridgepole/dsl_parser/context.rb +16 -0
- 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/version.rb +1 -1
- data/lib/ridgepole.rb +3 -0
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a14d5a37589fdd2639feba516071c7593f98f05f485c96401dca5f3021674330
|
4
|
+
data.tar.gz: 96747ebd0c5da3c8e77726e839b721aeb78c088d7d6dfd25476ac6f34421d7fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 946b32832ab928d03a22caa78fe4bd6379778732817de10d9df12eb976d1feb0260cacb436ae848f47e98cae923542b4494e26690a78069dd5559a6278799286
|
7
|
+
data.tar.gz: 3bd4e975d7aa90162d3813f57215db79c5c0680553cb46e62cf820c4dfe6500af8c8f0845fe4799e481dfad453698439e84362d5f19ee2c0aafa454d28eae62a
|
data/.rubocop.yml
CHANGED
@@ -24,10 +24,18 @@ Layout/LineLength:
|
|
24
24
|
Enabled: false
|
25
25
|
Metrics/MethodLength:
|
26
26
|
Enabled: false
|
27
|
+
Metrics/ModuleLength:
|
28
|
+
Max: 106
|
27
29
|
Metrics/ParameterLists:
|
28
30
|
Enabled: false
|
29
31
|
Metrics/PerceivedComplexity:
|
30
32
|
Enabled: false
|
33
|
+
Naming/MethodName:
|
34
|
+
Exclude:
|
35
|
+
- "lib/ridgepole/ext/abstract_mysql_adapter/schema_creation.rb"
|
36
|
+
Naming/MethodParameterName:
|
37
|
+
Exclude:
|
38
|
+
- "lib/ridgepole/ext/abstract_mysql_adapter/schema_creation.rb"
|
31
39
|
Style/Documentation:
|
32
40
|
Enabled: false
|
33
41
|
Style/GuardClause:
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
## 1.0
|
4
4
|
|
5
|
+
### 1.0.2 (Unreleased)
|
6
|
+
|
7
|
+
* Add support for partitioning ([pull#374](https://github.com/ridgepole/ridgepole/pull/374))
|
8
|
+
* Suppress warning of table option differences ([pull#378](https://github.com/ridgepole/ridgepole/pull/378))
|
9
|
+
|
5
10
|
### 1.0.1 (2022/01/15)
|
6
11
|
|
7
12
|
* Fix code for RuboCop 1.24.1
|
data/README.md
CHANGED
@@ -301,6 +301,31 @@ Apply `Schemafile`
|
|
301
301
|
...
|
302
302
|
```
|
303
303
|
|
304
|
+
## Partitioning
|
305
|
+
|
306
|
+
**Notice:** PostgreSQL `PARTITION BY` must be specified with the create_table option.
|
307
|
+
|
308
|
+
### List Partitioning
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
create_table "articles", force: :cascade, options: "PARTITION BY LIST(id)" do |t|
|
312
|
+
end
|
313
|
+
|
314
|
+
add_partition("articles", :list, :id, partition_definitions: [{ name: 'p0', values: { in: [0,1,2] } }, { name: 'p1', values: { in: [3,4,5] } }])
|
315
|
+
```
|
316
|
+
|
317
|
+
### Range Partitioning
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
create_table "articles", force: :cascade, options: "PARTITION BY RANGE(id)" do |t|
|
321
|
+
end
|
322
|
+
|
323
|
+
# postgresql
|
324
|
+
add_partition("articles", :range, :id, partition_definitions: [{ name: 'p0', values: { from: 'MINVALUE', to: 5 }}, { name: 'p1', values: { from: 5, to: 10 } }])
|
325
|
+
# mysql
|
326
|
+
add_partition("articles", :range, :id, partition_definitions: [{ name: 'p0', values: { to: 5 }}, { name: 'p1', values: { to: 10 } }])
|
327
|
+
```
|
328
|
+
|
304
329
|
## Run tests
|
305
330
|
|
306
331
|
|
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,6 +37,9 @@ 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)
|
@@ -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
|
@@ -612,6 +615,54 @@ module Ridgepole
|
|
612
615
|
end
|
613
616
|
end
|
614
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
|
+
|
615
666
|
def check_table_existence(definition)
|
616
667
|
return unless @options[:tables]
|
617
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
|
|
@@ -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
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
4
|
+
|
5
|
+
module Ridgepole
|
6
|
+
module Ext
|
7
|
+
module PostgreSQLAdapter
|
8
|
+
module Partitioning
|
9
|
+
def supports_partitions?
|
10
|
+
ActiveRecord::VERSION::MAJOR >= 6 && postgresql_version >= 100_000 # >= 10.0
|
11
|
+
end
|
12
|
+
|
13
|
+
def table_options(table_name)
|
14
|
+
options = partition_options(table_name)
|
15
|
+
if options
|
16
|
+
(super || {}).merge(options: "PARTITION BY #{options[:type].to_s.upcase}(#{options[:columns].join(',')})")
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def partition_options(table_name)
|
23
|
+
return unless supports_partitions?
|
24
|
+
|
25
|
+
scope = quoted_scope(table_name)
|
26
|
+
result = query_value(<<~SQL, 'SCHEMA')
|
27
|
+
SELECT pg_get_partkeydef(t.oid)
|
28
|
+
FROM pg_class t
|
29
|
+
LEFT JOIN pg_namespace n ON n.oid = t.relnamespace
|
30
|
+
WHERE t.relname = #{scope[:name]}
|
31
|
+
AND n.nspname = #{scope[:schema]}
|
32
|
+
SQL
|
33
|
+
return unless result
|
34
|
+
|
35
|
+
type, *columns = result.scan(/\w+/).map { |value| value.downcase.to_sym }
|
36
|
+
{ type: type, columns: columns }
|
37
|
+
end
|
38
|
+
|
39
|
+
def partition(table_name)
|
40
|
+
options = partition_options(table_name)
|
41
|
+
return unless options
|
42
|
+
|
43
|
+
scope = quoted_scope(table_name)
|
44
|
+
partition_info = query(<<~SQL, 'SCHEMA')
|
45
|
+
SELECT p.relname, pg_get_expr(p.relpartbound, p.oid, true)
|
46
|
+
FROM pg_class t
|
47
|
+
JOIN pg_inherits i on i.inhparent = t.oid
|
48
|
+
JOIN pg_class p on p.oid = i.inhrelid
|
49
|
+
WHERE t.relname = #{scope[:name]}
|
50
|
+
AND p.relnamespace::regnamespace::text = #{scope[:schema]}
|
51
|
+
ORDER BY p.relname
|
52
|
+
SQL
|
53
|
+
|
54
|
+
partition_definitions = partition_info.map do |row|
|
55
|
+
values = case options[:type]
|
56
|
+
when :list
|
57
|
+
values = row[1].match(/FOR VALUES IN \((?<csv>.+)\)$/)[:csv].split(',').map(&:strip).map { |value| cast_value(value) }
|
58
|
+
{ in: Array.wrap(values) }
|
59
|
+
when :range
|
60
|
+
match = row[1].match(/FOR VALUES FROM \((?<from>.+)\) TO \((?<to>.+)\)/)
|
61
|
+
from = match[:from].split(',').map(&:strip).map { |value| cast_value(value) }
|
62
|
+
to = match[:to].split(',').map(&:strip).map { |value| cast_value(value) }
|
63
|
+
{ from: from, to: to }
|
64
|
+
else
|
65
|
+
raise NotImplementedError
|
66
|
+
end
|
67
|
+
{ name: row[0], values: values }
|
68
|
+
end
|
69
|
+
|
70
|
+
ActiveRecord::ConnectionAdapters::PartitionOptions.new(table_name, options[:type], options[:columns], partition_definitions: partition_definitions)
|
71
|
+
end
|
72
|
+
|
73
|
+
def cast_value(value)
|
74
|
+
Integer(value)
|
75
|
+
rescue ArgumentError
|
76
|
+
value.delete(%q("')) # "
|
77
|
+
end
|
78
|
+
|
79
|
+
def quote_value(value)
|
80
|
+
if %w[MINVALUE MAXVALUE].include?(value)
|
81
|
+
value
|
82
|
+
else
|
83
|
+
quote(value)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def partition_tables
|
88
|
+
partition_info = query(<<~SQL, 'SCHEMA')
|
89
|
+
SELECT p.relname
|
90
|
+
FROM pg_class t
|
91
|
+
JOIN pg_inherits i on i.inhparent = t.oid
|
92
|
+
JOIN pg_class p on p.oid = i.inhrelid
|
93
|
+
ORDER BY p.relname
|
94
|
+
SQL
|
95
|
+
partition_info.map { |row| row[0] }
|
96
|
+
end
|
97
|
+
|
98
|
+
# SchemaStatements
|
99
|
+
def add_partition(table_name, name:, values:)
|
100
|
+
condition = if values.key?(:in)
|
101
|
+
"FOR VALUES IN (#{values[:in].map { |v| quote_value(v) }.join(',')})"
|
102
|
+
elsif values.key?(:to)
|
103
|
+
from = values[:from].map { |v| quote_value(v) }.join(',')
|
104
|
+
to = values[:to].map { |v| quote_value(v) }.join(',')
|
105
|
+
"FOR VALUES FROM (#{from}) TO (#{to})"
|
106
|
+
else
|
107
|
+
raise NotImplementedError
|
108
|
+
end
|
109
|
+
create_table(name, id: false, options: "PARTITION OF #{table_name} #{condition}")
|
110
|
+
end
|
111
|
+
|
112
|
+
def remove_partition(_table_name, name:)
|
113
|
+
drop_table(name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
module ActiveRecord
|
121
|
+
module ConnectionAdapters
|
122
|
+
class PostgreSQLAdapter
|
123
|
+
prepend Ridgepole::Ext::PostgreSQLAdapter::Partitioning
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -45,6 +45,30 @@ module Ridgepole
|
|
45
45
|
stream.puts add_foreign_key_statements.sort.join("\n")
|
46
46
|
end
|
47
47
|
end
|
48
|
+
|
49
|
+
def tables(stream)
|
50
|
+
original = ignore_tables.dup
|
51
|
+
ignore_tables.concat(@connection.partition_tables)
|
52
|
+
super
|
53
|
+
ensure
|
54
|
+
self.ignore_tables = original
|
55
|
+
end
|
56
|
+
|
57
|
+
def table(table, stream)
|
58
|
+
super
|
59
|
+
partition(table, stream)
|
60
|
+
end
|
61
|
+
|
62
|
+
def partition(table, stream)
|
63
|
+
if (partition = @connection.partition(table))
|
64
|
+
partition_definitions = partition.partition_definitions.map do |partition_definition|
|
65
|
+
"{ name: #{partition_definition.name.inspect}, values: #{partition_definition.values} }"
|
66
|
+
end.join(' ,')
|
67
|
+
|
68
|
+
stream.puts " add_partition #{partition.table.inspect}, #{partition.type.inspect}, #{partition.columns.inspect}, partition_definitions: [#{partition_definitions}]"
|
69
|
+
stream.puts
|
70
|
+
end
|
71
|
+
end
|
48
72
|
end
|
49
73
|
end
|
50
74
|
end
|
data/lib/ridgepole/version.rb
CHANGED
data/lib/ridgepole.rb
CHANGED
@@ -16,6 +16,9 @@ require 'diffy'
|
|
16
16
|
module Ridgepole; end
|
17
17
|
|
18
18
|
require 'ridgepole/ext/abstract_adapter/disable_table_options'
|
19
|
+
require 'ridgepole/ext/abstract_adapter/partition_definition'
|
20
|
+
require 'ridgepole/ext/abstract_adapter/partition_options'
|
21
|
+
require 'ridgepole/ext/abstract_adapter/partitioning'
|
19
22
|
require 'ridgepole/ext/pp_sort_hash'
|
20
23
|
require 'ridgepole/ext/schema_dumper'
|
21
24
|
require 'ridgepole/client'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ridgepole
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Genki Sugawara
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -306,7 +306,13 @@ files:
|
|
306
306
|
- lib/ridgepole/dumper.rb
|
307
307
|
- lib/ridgepole/execute_expander.rb
|
308
308
|
- lib/ridgepole/ext/abstract_adapter/disable_table_options.rb
|
309
|
+
- lib/ridgepole/ext/abstract_adapter/partition_definition.rb
|
310
|
+
- lib/ridgepole/ext/abstract_adapter/partition_options.rb
|
311
|
+
- lib/ridgepole/ext/abstract_adapter/partitioning.rb
|
309
312
|
- lib/ridgepole/ext/abstract_mysql_adapter/dump_auto_increment.rb
|
313
|
+
- lib/ridgepole/ext/abstract_mysql_adapter/partitioning.rb
|
314
|
+
- lib/ridgepole/ext/abstract_mysql_adapter/schema_creation.rb
|
315
|
+
- lib/ridgepole/ext/postgresql_adapter/partitioning.rb
|
310
316
|
- lib/ridgepole/ext/pp_sort_hash.rb
|
311
317
|
- lib/ridgepole/ext/schema_dumper.rb
|
312
318
|
- lib/ridgepole/external_sql_executer.rb
|
@@ -332,9 +338,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
332
338
|
version: 2.2.7
|
333
339
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
334
340
|
requirements:
|
335
|
-
- - "
|
341
|
+
- - ">"
|
336
342
|
- !ruby/object:Gem::Version
|
337
|
-
version:
|
343
|
+
version: 1.3.1
|
338
344
|
requirements: []
|
339
345
|
rubygems_version: 3.2.32
|
340
346
|
signing_key:
|