tablature 0.1.1 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eaec79244b29bb6514402673f8cb56f281c9cf25fb73c9a4dab413520e96b652
4
- data.tar.gz: 54cfe803f7387135d1334dba6b4b16a0c7d1946adb6c88e092889fe881203511
3
+ metadata.gz: 37da9dc068662c878b97c2f7cf95274f9893d4a34940d9511db0933d98b3597c
4
+ data.tar.gz: 261087c9165cba3aa60d4999f398889725979fa0398a5775c2408c0e41a420bb
5
5
  SHA512:
6
- metadata.gz: f30ffd3c9b8b14b08447b222a2bc5202042ef37f2c530beda33ed651c47ea7095f536bec494eef5767c44ba7a9b9751578bd9e3ff497e53203ac9bba477fc8a2
7
- data.tar.gz: 2553bb52f8fecfa642cf2b8c1f3908bcabf01c3a9c6e5902076e7d447e6cf64c8bd13069902d95cc7e4478daff7a08b1d899128b74722687ad5a0dcf6e0962a0
6
+ metadata.gz: e66f6acf2ae0bbc061b92153b5f3ba798dd37f69302e2f6de5b6cf5363bdc3d8ce3bed6442017b6d85ef4aab52e96301056f72a16a393282396042c4d54334e1
7
+ data.tar.gz: 3855a566bc693bf97c7b20b2871703bb766fcd2553ef1e0043d5d18c47d7cc80eb60e779b0d2d9baf83aa2447379f9486de7104cb05945b2cc86adc9485fd537
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tablature (0.1.1)
4
+ tablature (0.2.0)
5
5
  activerecord (>= 5.0.0)
6
6
  railties (>= 5.0.0)
7
7
 
@@ -35,21 +35,21 @@ GEM
35
35
  arel (9.0.0)
36
36
  builder (3.2.3)
37
37
  coderay (1.1.2)
38
- concurrent-ruby (1.1.3)
38
+ concurrent-ruby (1.1.4)
39
39
  crass (1.0.4)
40
40
  database_cleaner (1.7.0)
41
41
  diff-lcs (1.3)
42
- erubi (1.7.1)
43
- i18n (1.1.1)
42
+ erubi (1.8.0)
43
+ i18n (1.5.1)
44
44
  concurrent-ruby (~> 1.0)
45
45
  loofah (2.2.3)
46
46
  crass (~> 1.0.2)
47
47
  nokogiri (>= 1.5.9)
48
48
  method_source (0.9.0)
49
- mini_portile2 (2.3.0)
49
+ mini_portile2 (2.4.0)
50
50
  minitest (5.11.3)
51
- nokogiri (1.8.5)
52
- mini_portile2 (~> 2.3.0)
51
+ nokogiri (1.10.0)
52
+ mini_portile2 (~> 2.4.0)
53
53
  pg (0.21.0)
54
54
  pry (0.12.2)
55
55
  coderay (~> 1.1.0)
@@ -103,4 +103,4 @@ DEPENDENCIES
103
103
  tablature!
104
104
 
105
105
  BUNDLED WITH
106
- 1.16.6
106
+ 1.17.2
data/README.md CHANGED
@@ -55,14 +55,69 @@ class CreateEvents < ActiveRecord::Migration[5.0]
55
55
  # Create partitions with the bounds of the partition.
56
56
  create_list_partition_of :events_by_list,
57
57
  name: 'events_list_y2018m12', values: (Date.parse('2018-12-01')..Date.parse('2018-12-31')).to_a
58
+
58
59
  end
59
60
 
60
61
  def down
61
- drop_table :events
62
+ drop_table :events_by_range
63
+ drop_table :events_by_list
62
64
  end
63
65
  end
64
66
  ```
65
67
 
68
+ ### Having a partition back a model
69
+
70
+
71
+ In your migration:
72
+ ```ruby
73
+ # db/migrate/create_events.rb
74
+ class CreateEvents < ActiveRecord::Migration
75
+ def change
76
+ # You can use blocks when the partition key are SQL expression instead of
77
+ # being only a field.
78
+ create_range_partition :events, partition_key: -> { '(timestamp::DATE)' } do |t|
79
+ t.string :event_type, null: false
80
+ t.integer :value, null: false
81
+ t.datetime :timestamp, null: false
82
+ t.timestamps
83
+ end
84
+
85
+ create_range_partition_of :events,
86
+ name: 'events_y2018m12', range_start: '2018-12-01', range_end: '2019-01-01'
87
+
88
+ create_range_partition_of :events,
89
+ name: 'events_y2019m01', range_start: '2019-01-01', range_end: '2019-02-01'
90
+ end
91
+ end
92
+ ```
93
+
94
+ In your model, calling one of `range_partition` or `list_partition` to inject
95
+ methods:
96
+ ```
97
+ # app/models/event.rb
98
+ class Event < ApplicationRecord
99
+ range_partition
100
+ end
101
+ ```
102
+
103
+ Finally, you can now list the partitions :
104
+ ```ruby
105
+ >> Event.partitions
106
+ # => ["events_y2018m12", "events_y2019m01"]
107
+ ```
108
+
109
+ You can also create new partitions directly from the model :
110
+ ```ruby
111
+ >> Event.create_range_partition(
112
+ name: 'events_y2019m02',
113
+ range_start: '2019-02-01'.to_date,
114
+ range_end: '2019-03-01'.to_date
115
+ )
116
+ # => ...
117
+ >> Event.partitions
118
+ # => ["events_y2018m12", "events_y2019m01", "events_y2019m02"]
119
+ ```
120
+
66
121
  ## Development
67
122
 
68
123
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -25,7 +25,8 @@ module Tablature
25
25
  c.oid,
26
26
  c.relname AS table_name,
27
27
  p.partstrat AS type,
28
- (i.inhrelid::REGCLASS)::TEXT AS partition_name
28
+ (i.inhrelid::REGCLASS)::TEXT AS partition_name,
29
+ pg_get_partkeydef(c.oid) AS partition_key_definition
29
30
  FROM pg_class c
30
31
  INNER JOIN pg_partitioned_table p ON c.oid = p.partrelid
31
32
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -47,11 +48,17 @@ module Tablature
47
48
 
48
49
  def to_tablature_table(table_name, rows)
49
50
  result = rows.first
50
- partioning_method = METHOD_MAP.fetch(result['type'])
51
+ partitioning_method = METHOD_MAP.fetch(result['type'])
51
52
  partitions = rows.map { |row| row['partition_name'] }.compact.map(&method(:unquote))
53
+ # This is very fragile code. This makes the assumption that:
54
+ # - Postgres will always have a function `pg_get_partkeydef` that returns the partition
55
+ # method with the partition key
56
+ # - Postgres will never have a partition method with two words in its name.
57
+ _, partition_key = result['partition_key_definition'].split(' ', 2)
52
58
 
53
59
  Tablature::PartitionedTable.new(
54
- name: table_name, partioning_method: partioning_method, partitions: partitions
60
+ name: table_name, partitioning_method: partitioning_method,
61
+ partitions: partitions, partition_key: partition_key
55
62
  )
56
63
  end
57
64
 
@@ -4,7 +4,11 @@ module Tablature
4
4
  # @api private
5
5
  module Quoting
6
6
  def quote_partition_key(key)
7
- key.to_s.split('::').map(&method(:quote_column_name)).join('::')
7
+ if key.respond_to?(:call)
8
+ key.call.to_s
9
+ else
10
+ key.to_s.split('::').map(&method(:quote_column_name)).join('::')
11
+ end
8
12
  end
9
13
 
10
14
  def quote_collection(values)
@@ -47,7 +47,7 @@ module Tablature
47
47
  # @param [String, Symbol] table_name The name of the table to partition.
48
48
  # @param [Hash] options The options to create the partition. Keys besides +:partition_key+
49
49
  # will be passed to +create_table+.
50
- # @option options [String, Symbol] :partition_key The partition key.
50
+ # @option options [String, Symbol, #call] :partition_key The partition key.
51
51
  # @yield [td] A TableDefinition object. This allows creating the table columns the same way
52
52
  # as Rails's +create_table+ does.
53
53
  #
@@ -84,7 +84,6 @@ module Tablature
84
84
  # @param [String, Symbol] table_name The name of the table to partition.
85
85
  # @param [Hash] options The options to create the partition. Keys besides +:partition_key+
86
86
  # will be passed to +create_table+.
87
- # @option options [String, Symbol] :partition_key The partition key.
88
87
  # @yield [td] A TableDefinition object. This allows creating the table columns the same way
89
88
  # as Rails's +create_table+ does.
90
89
  #
@@ -0,0 +1,29 @@
1
+ require 'forwardable'
2
+
3
+ module Tablature
4
+ module Model
5
+ module PartitionMethods
6
+ extend Forwardable
7
+ def_delegators :tablature_partition, :partitions, :partition_key, :partitioning_method
8
+
9
+ def tablature_partition
10
+ partition = Tablature.database.partitioned_tables.find do |pt|
11
+ pt.name == partition_name.to_s
12
+ end
13
+ raise Tablature::MissingPartition if partition.nil?
14
+
15
+ partition
16
+ end
17
+
18
+ def partitioned?
19
+ begin
20
+ tablature_partition
21
+ rescue Tablature::MissingPartition
22
+ return false
23
+ end
24
+
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,4 +1,51 @@
1
+ require 'tablature/model/partition_methods'
2
+
1
3
  module Tablature
2
4
  module Model
5
+ module ListPartitionMethods
6
+ def create_list_partition(options)
7
+ Tablature.database.create_list_partition_of(tablature_partition.name, options)
8
+ end
9
+ end
10
+
11
+ module RangePartitionMethods
12
+ def create_range_partition(options)
13
+ Tablature.database.create_range_partition_of(tablature_partition.name, options)
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def list_partition(partition_name = table_name)
19
+ setup_partition(partition_name)
20
+ extend(PartitionMethods)
21
+ extend(ListPartitionMethods)
22
+ end
23
+
24
+ def range_partition(partition_name = table_name)
25
+ setup_partition(partition_name)
26
+ extend(PartitionMethods)
27
+ extend(RangePartitionMethods)
28
+ end
29
+
30
+ # @api private
31
+ def inspect
32
+ return super unless partitioned?
33
+
34
+ # Copied from the Rails source.
35
+ attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ', '
36
+ "#{self}(#{attr_list})"
37
+ end
38
+
39
+ private
40
+
41
+ def setup_partition(partition_name)
42
+ class_attribute(:partition_name)
43
+ self.partition_name = partition_name
44
+ end
45
+ end
46
+
47
+ def self.included(klass)
48
+ klass.extend ClassMethods
49
+ end
3
50
  end
4
51
  end
@@ -13,21 +13,27 @@ module Tablature
13
13
 
14
14
  # The partitioning method of the table
15
15
  # @return [Symbol]
16
- attr_reader :partioning_method
16
+ attr_reader :partitioning_method
17
17
 
18
18
  # The partitions of the table.
19
19
  # @return [Array]
20
20
  attr_reader :partitions
21
21
 
22
+ # The partition key expression.
23
+ # @return [String]
24
+ attr_reader :partition_key
25
+
22
26
  # Returns a new instance of PartitionTable.
23
27
  #
24
28
  # @param name [String] The name of the view.
25
- # @param partioning_method [:symbol] One of :range, :list or :hash
29
+ # @param partitioning_method [:symbol] One of :range, :list or :hash
26
30
  # @param partitions [Array] The partitions of the table.
27
- def initialize(name:, partioning_method:, partitions: [])
31
+ # @param partition_key [String] The partition key expression.
32
+ def initialize(name:, partitioning_method:, partitions: [], partition_key:)
28
33
  @name = name
29
- @partioning_method = partioning_method
34
+ @partitioning_method = partitioning_method
30
35
  @partitions = partitions
36
+ @partition_key = partition_key
31
37
  end
32
38
  end
33
39
  end
@@ -1,3 +1,3 @@
1
1
  module Tablature
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/lib/tablature.rb CHANGED
@@ -20,7 +20,7 @@ module Tablature
20
20
  ActiveRecord::ConnectionAdapters::AbstractAdapter.include Tablature::Statements
21
21
  ActiveRecord::Migration::CommandRecorder.include Tablature::CommandRecorder
22
22
  ActiveRecord::SchemaDumper.prepend Tablature::SchemaDumper
23
- ActiveRecord::Base.prepend Tablature::Model
23
+ ActiveRecord::Base.include Tablature::Model
24
24
  end
25
25
 
26
26
  # The current database adapter used by Tablature.
@@ -29,4 +29,10 @@ module Tablature
29
29
  def self.database
30
30
  configuration.database
31
31
  end
32
+
33
+ class MissingPartition < StandardError
34
+ def initialize
35
+ super('Missing partition')
36
+ end
37
+ end
32
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tablature
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
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: 2019-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -154,6 +154,7 @@ files:
154
154
  - lib/tablature/command_recorder.rb
155
155
  - lib/tablature/configuration.rb
156
156
  - lib/tablature/model.rb
157
+ - lib/tablature/model/partition_methods.rb
157
158
  - lib/tablature/partitioned_table.rb
158
159
  - lib/tablature/railtie.rb
159
160
  - lib/tablature/schema_dumper.rb