tablature 0.1.1 → 1.0.0.pre2
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/.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
|