tablature 0.1.1 → 1.0.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +68 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -1
- data/.yardopts +1 -0
- data/Gemfile +8 -0
- data/README.md +99 -2
- data/lib/tablature.rb +8 -1
- data/lib/tablature/adapters/postgres.rb +76 -5
- data/lib/tablature/adapters/postgres/connection.rb +14 -0
- data/lib/tablature/adapters/postgres/errors.rb +19 -2
- data/lib/tablature/adapters/postgres/handlers/base.rb +4 -0
- data/lib/tablature/adapters/postgres/handlers/list.rb +57 -3
- data/lib/tablature/adapters/postgres/handlers/range.rb +62 -3
- data/lib/tablature/adapters/postgres/indexes.rb +96 -0
- data/lib/tablature/adapters/postgres/partitioned_tables.rb +20 -8
- data/lib/tablature/adapters/postgres/quoting.rb +5 -0
- data/lib/tablature/command_recorder.rb +93 -0
- data/lib/tablature/model.rb +70 -0
- data/lib/tablature/partition.rb +23 -0
- data/lib/tablature/partitioned_table.rb +51 -7
- data/lib/tablature/schema_dumper.rb +66 -3
- data/lib/tablature/statements.rb +67 -11
- data/lib/tablature/version.rb +1 -1
- data/tablature.gemspec +6 -4
- metadata +18 -19
- data/.circleci/config.yml +0 -81
- data/Gemfile.lock +0 -106
- data/bin/console +0 -10
- data/bin/setup +0 -8
@@ -0,0 +1,23 @@
|
|
1
|
+
module Tablature
|
2
|
+
# The in-memory representation of a partition.
|
3
|
+
#
|
4
|
+
# **This object is used internally by adapters and the schema dumper and is
|
5
|
+
# not intended to be used by application code. It is documented here for
|
6
|
+
# use by adapter gems.**
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Partition
|
10
|
+
attr_reader :name
|
11
|
+
attr_reader :parent_table_name
|
12
|
+
|
13
|
+
def initialize(name:, parent_table_name:, default_partition: false)
|
14
|
+
@name = name
|
15
|
+
@parent_table_name = parent_table_name
|
16
|
+
@default_partition = default_partition
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_partition?
|
20
|
+
@default_partition
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -5,29 +5,73 @@ module Tablature
|
|
5
5
|
# not intended to be used by application code. It is documented here for
|
6
6
|
# use by adapter gems.**
|
7
7
|
#
|
8
|
-
# @api
|
8
|
+
# @api private
|
9
9
|
class PartitionedTable
|
10
10
|
# The name of the partitioned table
|
11
11
|
# @return [String]
|
12
|
+
# @api private
|
12
13
|
attr_reader :name
|
13
14
|
|
14
|
-
# The partitioning
|
15
|
+
# The partitioning strategy of the table
|
15
16
|
# @return [Symbol]
|
16
|
-
|
17
|
+
# @api private
|
18
|
+
attr_reader :partitioning_strategy
|
17
19
|
|
18
20
|
# The partitions of the table.
|
19
21
|
# @return [Array]
|
22
|
+
# @api private
|
20
23
|
attr_reader :partitions
|
21
24
|
|
25
|
+
# The partition key expression.
|
26
|
+
# @return [String]
|
27
|
+
# @api private
|
28
|
+
attr_reader :partition_key
|
29
|
+
|
22
30
|
# Returns a new instance of PartitionTable.
|
23
31
|
#
|
24
32
|
# @param name [String] The name of the view.
|
25
|
-
# @param
|
33
|
+
# @param partitioning_strategy [:symbol] One of :range, :list or :hash
|
26
34
|
# @param partitions [Array] The partitions of the table.
|
27
|
-
|
35
|
+
# @param partition_key [String] The partition key expression.
|
36
|
+
# @api private
|
37
|
+
def initialize(name:, partitioning_strategy:, partitions: [], partition_key:)
|
28
38
|
@name = name
|
29
|
-
@
|
30
|
-
@partitions = partitions
|
39
|
+
@partitioning_strategy = partitioning_strategy
|
40
|
+
@partitions = partitions.map do |row|
|
41
|
+
Tablature::Partition.new(
|
42
|
+
name: row['partition_name'],
|
43
|
+
parent_table_name: row['table_name'],
|
44
|
+
default_partition: row['is_default_partition']
|
45
|
+
)
|
46
|
+
end
|
47
|
+
@partition_key = partition_key
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the representation of the default partition if present.
|
51
|
+
#
|
52
|
+
# @return [Tablature::Partition]
|
53
|
+
# @api private
|
54
|
+
def default_partition
|
55
|
+
partitions.find(&:default_partition?)
|
56
|
+
end
|
57
|
+
|
58
|
+
PARTITION_METHOD_MAP = {
|
59
|
+
list: 'create_list_partition',
|
60
|
+
range: 'create_range_partition'
|
61
|
+
}.freeze
|
62
|
+
private_constant :PARTITION_METHOD_MAP
|
63
|
+
|
64
|
+
def to_schema
|
65
|
+
return nil unless PARTITION_METHOD_MAP.key?(partitioning_strategy)
|
66
|
+
|
67
|
+
creation_method = PARTITION_METHOD_MAP[partitioning_strategy]
|
68
|
+
<<-CONTENT
|
69
|
+
#{creation_method} #{name.inspect}, partition_key: #{partition_key.inspect} do |t|
|
70
|
+
CONTENT
|
71
|
+
end
|
72
|
+
|
73
|
+
def <=>(other)
|
74
|
+
name <=> other.name
|
31
75
|
end
|
32
76
|
end
|
33
77
|
end
|
@@ -1,22 +1,85 @@
|
|
1
|
+
# TODO: Try to replace the creation methods in the main stream instead of dumping the partitioned
|
2
|
+
# tables at the end of the schema.
|
1
3
|
module Tablature
|
2
4
|
# @api private
|
3
5
|
module SchemaDumper
|
4
6
|
def tables(stream)
|
5
7
|
# Add partitions to the list of ignored tables.
|
6
8
|
ActiveRecord::SchemaDumper.ignore_tables =
|
7
|
-
(ActiveRecord::SchemaDumper.ignore_tables || []) +
|
9
|
+
(ActiveRecord::SchemaDumper.ignore_tables || []) +
|
10
|
+
dumpable_partitioned_tables.map(&:name) +
|
11
|
+
partitions
|
8
12
|
|
9
13
|
super
|
14
|
+
|
15
|
+
partitioned_tables(stream)
|
16
|
+
end
|
17
|
+
|
18
|
+
def partitioned_tables(stream)
|
19
|
+
stream.puts if dumpable_partitioned_tables.any?
|
20
|
+
|
21
|
+
dumpable_partitioned_tables.each do |partitioned_table|
|
22
|
+
dump_partitioned_table(partitioned_table, stream)
|
23
|
+
dump_partition_indexes(partitioned_table, stream)
|
24
|
+
dump_foreign_keys(partitioned_table, stream)
|
25
|
+
end
|
10
26
|
end
|
11
27
|
|
12
28
|
private
|
13
29
|
|
30
|
+
attr_reader :connection
|
31
|
+
delegate :quote_table_name, :quote, to: :connection
|
32
|
+
|
33
|
+
PARTITION_METHOD_MAP = {
|
34
|
+
list: 'create_list_partition',
|
35
|
+
range: 'create_range_partition'
|
36
|
+
}.freeze
|
37
|
+
private_constant :PARTITION_METHOD_MAP
|
38
|
+
|
39
|
+
def dump_partitioned_table(partitioned_table, main_stream)
|
40
|
+
# Pretend the partitioned table is a regular table and dump it in an alternate stream.
|
41
|
+
stream = StringIO.new
|
42
|
+
table(partitioned_table.name, stream)
|
43
|
+
|
44
|
+
header = partitioned_table.to_schema
|
45
|
+
if header.nil?
|
46
|
+
main_stream.puts <<~MESSAGE
|
47
|
+
# Unknown partitioning strategy "#{partitioned_table.partitioning_strategy}" for partitioned table "#{partitioned_table.name}".
|
48
|
+
# Dumping table as a regular table.
|
49
|
+
MESSAGE
|
50
|
+
main_stream.puts(stream.tap(&:rewind).read)
|
51
|
+
else
|
52
|
+
content = stream.tap(&:rewind).read.gsub(/create_table.*/, header)
|
53
|
+
main_stream.puts(content)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Delegate to the adapter the dumping of indexes.
|
58
|
+
def dump_partition_indexes(partitioned_table, stream)
|
59
|
+
return unless Tablature.database.respond_to?(:indexes_on)
|
60
|
+
|
61
|
+
indexes = Tablature.database.indexes_on(partitioned_table.name)
|
62
|
+
return if indexes.empty?
|
63
|
+
|
64
|
+
add_index_statements = indexes.map do |index|
|
65
|
+
table_name = remove_prefix_and_suffix(index.table).inspect
|
66
|
+
" add_index #{([table_name] + index_parts(index)).join(', ')}"
|
67
|
+
end
|
68
|
+
|
69
|
+
stream.puts add_index_statements.sort.join("\n")
|
70
|
+
stream.puts
|
71
|
+
end
|
72
|
+
|
73
|
+
def dump_foreign_keys(partitioned_table, stream)
|
74
|
+
foreign_keys(partitioned_table.name, stream)
|
75
|
+
end
|
76
|
+
|
14
77
|
def dumpable_partitioned_tables
|
15
|
-
Tablature.database.partitioned_tables
|
78
|
+
Tablature.database.partitioned_tables.sort
|
16
79
|
end
|
17
80
|
|
18
81
|
def partitions
|
19
|
-
dumpable_partitioned_tables.flat_map(&:
|
82
|
+
dumpable_partitioned_tables.flat_map { |t| t.partitions.map(&:name) }
|
20
83
|
end
|
21
84
|
end
|
22
85
|
end
|
data/lib/tablature/statements.rb
CHANGED
@@ -3,15 +3,15 @@ module Tablature
|
|
3
3
|
module Statements
|
4
4
|
# Creates a partitioned table using the list partition method.
|
5
5
|
#
|
6
|
-
# @param
|
6
|
+
# @param table_name [String, Symbol] The name of the partition.
|
7
7
|
# @param options [Hash] The options to create the partition.
|
8
8
|
# @yield [td] A TableDefinition object. This allows creating the table columns the same way
|
9
9
|
# as Rails's +create_table+ does.
|
10
10
|
# @see Tablature::Adapters::Postgres#create_list_partition
|
11
|
-
def create_list_partition(
|
11
|
+
def create_list_partition(table_name, options, &block)
|
12
12
|
raise ArgumentError, 'partition_key must be defined' if options[:partition_key].nil?
|
13
13
|
|
14
|
-
Tablature.database.create_list_partition(
|
14
|
+
Tablature.database.create_list_partition(table_name, options, &block)
|
15
15
|
end
|
16
16
|
|
17
17
|
# Creates a partition of a parent by specifying the key values appearing in the partition.
|
@@ -21,23 +21,52 @@ module Tablature
|
|
21
21
|
#
|
22
22
|
# @see Tablature::Adapters::Postgres#create_list_partition_of
|
23
23
|
def create_list_partition_of(parent_table_name, options)
|
24
|
-
|
24
|
+
if options[:values].blank? && options[:default].blank?
|
25
|
+
raise ArgumentError, 'values or default must be defined'
|
26
|
+
end
|
25
27
|
|
26
28
|
Tablature.database.create_list_partition_of(parent_table_name, options)
|
27
29
|
end
|
28
30
|
|
31
|
+
# Attaches a partition to a parent by specifying the key values appearing in the partition.
|
32
|
+
#
|
33
|
+
# @param parent_table_name [String, Symbol] The name of the parent table.
|
34
|
+
# @param [Hash] options The options to attach the partition.
|
35
|
+
#
|
36
|
+
# @see Tablature::Adapters::Postgres#attach_to_list_partition
|
37
|
+
def attach_to_list_partition(parent_table_name, options)
|
38
|
+
raise ArgumentError, 'name must be defined' if options[:name].blank?
|
39
|
+
|
40
|
+
Tablature.database.attach_to_list_partition(parent_table_name, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Detaches a partition from a parent.
|
44
|
+
#
|
45
|
+
# @param parent_table_name [String, Symbol] The name of the parent table.
|
46
|
+
# @param [Hash] options The options to create the partition.
|
47
|
+
#
|
48
|
+
# @see Tablature::Adapters::Postgres#detach_from_list_partition
|
49
|
+
def detach_from_list_partition(parent_table_name, options)
|
50
|
+
raise ArgumentError, 'name must be defined' if options[:name].blank?
|
51
|
+
if options[:values].blank? && options[:default].blank?
|
52
|
+
raise ArgumentError, 'values or default must be defined'
|
53
|
+
end
|
54
|
+
|
55
|
+
Tablature.database.attach_to_list_partition(parent_table_name, options)
|
56
|
+
end
|
57
|
+
|
29
58
|
# Creates a partitioned table using the range partition method.
|
30
59
|
#
|
31
|
-
# @param
|
60
|
+
# @param table_name [String, Symbol] The name of the partition.
|
32
61
|
# @param options [Hash] The options to create the partition.
|
33
62
|
# @yield [td] A TableDefinition object. This allows creating the table columns the same way
|
34
63
|
# as Rails's +create_table+ does.
|
35
64
|
#
|
36
65
|
# @see Tablature::Adapters::Postgres#create_range_partition
|
37
|
-
def create_range_partition(
|
66
|
+
def create_range_partition(table_name, options, &block)
|
38
67
|
raise ArgumentError, 'partition_key must be defined' if options[:partition_key].nil?
|
39
68
|
|
40
|
-
Tablature.database.create_range_partition(
|
69
|
+
Tablature.database.create_range_partition(table_name, options, &block)
|
41
70
|
end
|
42
71
|
|
43
72
|
# Creates a partition of a parent by specifying the key values appearing in the partition.
|
@@ -46,12 +75,39 @@ module Tablature
|
|
46
75
|
# @param [Hash] options The options to create the partition.
|
47
76
|
#
|
48
77
|
# @see Tablature::Adapters::Postgres#create_range_partition_of
|
49
|
-
def create_range_partition_of(
|
50
|
-
if options[:range_start].nil? || options[:range_end].nil?
|
51
|
-
raise ArgumentError, 'range_start and range_end must be defined'
|
78
|
+
def create_range_partition_of(parent_table_name, options)
|
79
|
+
if (options[:range_start].nil? || options[:range_end].nil?) && options[:default].blank?
|
80
|
+
raise ArgumentError, 'range_start and range_end or default must be defined'
|
52
81
|
end
|
53
82
|
|
54
|
-
Tablature.database.create_range_partition_of(
|
83
|
+
Tablature.database.create_range_partition_of(parent_table_name, options)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Attaches a partition to a parent by specifying the key values appearing in the partition.
|
87
|
+
#
|
88
|
+
# @param parent_table_name [String, Symbol] The name of the parent table.
|
89
|
+
# @param [Hash] options The options to create the partition.
|
90
|
+
#
|
91
|
+
# @see Tablature::Adapters::Postgres#attach_to_range_partition
|
92
|
+
def attach_to_range_partition(parent_table_name, options)
|
93
|
+
raise ArgumentError, 'name must be defined' if options[:name].blank?
|
94
|
+
if (options[:range_start].nil? || options[:range_end].nil?) && options[:default].blank?
|
95
|
+
raise ArgumentError, 'range_start and range_end or default must be defined'
|
96
|
+
end
|
97
|
+
|
98
|
+
Tablature.database.attach_to_range_partition(parent_table_name, options)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Detaches a partition from a parent.
|
102
|
+
#
|
103
|
+
# @param parent_table_name [String, Symbol] The name of the parent table.
|
104
|
+
# @param [Hash] options The options to detach the partition.
|
105
|
+
#
|
106
|
+
# @see Tablature::Adapters::Postgres#detach_from_range_partition
|
107
|
+
def detach_from_range_partition(parent_table_name, options)
|
108
|
+
raise ArgumentError, 'name must be defined' if options[:name].blank?
|
109
|
+
|
110
|
+
Tablature.database.detach_from_range_partition(parent_table_name, options)
|
55
111
|
end
|
56
112
|
end
|
57
113
|
end
|
data/lib/tablature/version.rb
CHANGED
data/tablature.gemspec
CHANGED
@@ -14,16 +14,18 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
-
f.match(%r{^(test|spec|features)/})
|
17
|
+
f.match(%r{^(bin|test|spec|features)/})
|
18
18
|
end
|
19
19
|
spec.bindir = 'exe'
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ['lib']
|
22
22
|
|
23
|
-
spec.
|
23
|
+
spec.required_ruby_version = '>= 2.5.0'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler'
|
24
26
|
spec.add_development_dependency 'database_cleaner'
|
25
|
-
spec.add_development_dependency 'pg', '~>
|
26
|
-
spec.add_development_dependency 'rake', '~>
|
27
|
+
spec.add_development_dependency 'pg', '~> 1.1'
|
28
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
27
29
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
30
|
spec.add_development_dependency 'rspec-instafail'
|
29
31
|
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tablature
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.pre2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aliou Diallo
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: database_cleaner
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,28 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '1.1'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '1.1'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '13.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '13.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,18 +129,16 @@ executables: []
|
|
129
129
|
extensions: []
|
130
130
|
extra_rdoc_files: []
|
131
131
|
files:
|
132
|
-
- ".
|
132
|
+
- ".github/workflows/ci.yml"
|
133
133
|
- ".gitignore"
|
134
134
|
- ".rspec"
|
135
135
|
- ".rubocop.yml"
|
136
136
|
- ".travis.yml"
|
137
|
+
- ".yardopts"
|
137
138
|
- Gemfile
|
138
|
-
- Gemfile.lock
|
139
139
|
- LICENSE.txt
|
140
140
|
- README.md
|
141
141
|
- Rakefile
|
142
|
-
- bin/console
|
143
|
-
- bin/setup
|
144
142
|
- lib/tablature.rb
|
145
143
|
- lib/tablature/adapters/postgres.rb
|
146
144
|
- lib/tablature/adapters/postgres/connection.rb
|
@@ -148,12 +146,14 @@ files:
|
|
148
146
|
- lib/tablature/adapters/postgres/handlers/base.rb
|
149
147
|
- lib/tablature/adapters/postgres/handlers/list.rb
|
150
148
|
- lib/tablature/adapters/postgres/handlers/range.rb
|
149
|
+
- lib/tablature/adapters/postgres/indexes.rb
|
151
150
|
- lib/tablature/adapters/postgres/partitioned_tables.rb
|
152
151
|
- lib/tablature/adapters/postgres/quoting.rb
|
153
152
|
- lib/tablature/adapters/postgres/uuid.rb
|
154
153
|
- lib/tablature/command_recorder.rb
|
155
154
|
- lib/tablature/configuration.rb
|
156
155
|
- lib/tablature/model.rb
|
156
|
+
- lib/tablature/partition.rb
|
157
157
|
- lib/tablature/partitioned_table.rb
|
158
158
|
- lib/tablature/railtie.rb
|
159
159
|
- lib/tablature/schema_dumper.rb
|
@@ -172,15 +172,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
172
172
|
requirements:
|
173
173
|
- - ">="
|
174
174
|
- !ruby/object:Gem::Version
|
175
|
-
version:
|
175
|
+
version: 2.5.0
|
176
176
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
|
-
- - "
|
178
|
+
- - ">"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version:
|
180
|
+
version: 1.3.1
|
181
181
|
requirements: []
|
182
|
-
|
183
|
-
rubygems_version: 2.7.6
|
182
|
+
rubygems_version: 3.0.3
|
184
183
|
signing_key:
|
185
184
|
specification_version: 4
|
186
185
|
summary: Rails + Postgres Partitions
|