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.
@@ -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 extension
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 method of the table
15
+ # The partitioning strategy of the table
15
16
  # @return [Symbol]
16
- attr_reader :partioning_method
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 partioning_method [:symbol] One of :range, :list or :hash
33
+ # @param partitioning_strategy [:symbol] One of :range, :list or :hash
26
34
  # @param partitions [Array] The partitions of the table.
27
- def initialize(name:, partioning_method:, partitions: [])
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
- @partioning_method = partioning_method
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 || []) + partitions
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(&:partitions)
82
+ dumpable_partitioned_tables.flat_map { |t| t.partitions.map(&:name) }
20
83
  end
21
84
  end
22
85
  end
@@ -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 name [String, Symbol] The name of the partition.
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(name, options, &block)
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(name, options, &block)
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
- raise ArgumentError, 'values must be defined' if options[:values].nil?
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 name [String, Symbol] The name of the partition.
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(name, options, &block)
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(name, options, &block)
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(parent_table, options)
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(parent_table, options)
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
@@ -1,3 +1,3 @@
1
1
  module Tablature
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '1.0.0.pre2'.freeze
3
3
  end
@@ -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.add_development_dependency 'bundler', '~> 1.16'
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', '~> 0.19'
26
- spec.add_development_dependency 'rake', '~> 10.0'
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.1.1
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: 2018-12-05 00:00:00.000000000 Z
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: '1.16'
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: '1.16'
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: '0.19'
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: '0.19'
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: '10.0'
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: '10.0'
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
- - ".circleci/config.yml"
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: '0'
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: '0'
180
+ version: 1.3.1
181
181
  requirements: []
182
- rubyforge_project:
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