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.
@@ -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