pg_party 0.2.1 → 0.3.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 +4 -4
- data/README.md +177 -3
- data/lib/pg_party/{connection_adapters/abstract_adapter.rb → adapter/abstract_methods.rb} +9 -9
- data/lib/pg_party/adapter/postgresql_methods.rb +35 -0
- data/lib/pg_party/adapter_decorator.rb +133 -0
- data/lib/pg_party/model/list_methods.rb +23 -0
- data/lib/pg_party/model/methods.rb +19 -0
- data/lib/pg_party/model/range_methods.rb +23 -0
- data/lib/pg_party/model_decorator.rb +62 -0
- data/lib/pg_party/model_injector.rb +39 -0
- data/lib/pg_party/version.rb +1 -1
- data/lib/pg_party.rb +11 -6
- metadata +11 -10
- data/lib/pg_party/connection_adapters/postgresql_adapter.rb +0 -125
- data/lib/pg_party/connection_handling.rb +0 -15
- data/lib/pg_party/injected_list_model_methods.rb +0 -21
- data/lib/pg_party/injected_model_methods.rb +0 -23
- data/lib/pg_party/injected_range_model_methods.rb +0 -24
- data/lib/pg_party/model_methods.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 820e7b5d4a74330f2871d8e8d5e278974001dd1f
|
4
|
+
data.tar.gz: 80a59da979fa45c339aa3abe0201c154809a4eb4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e14b35c529df6ea91d093e444962be64d65a112ab706b67bdce500904fb74eb88c31fd944aa06c9386f8cd365286841723ea71ecb883086604912ce6484d6db
|
7
|
+
data.tar.gz: 622f8c66614c01ef3a2a649f6e0c9123b07a7d73d8adbd74ef2428215ff70d78a70e554f5bd4b98e246beb2327d174b5badce0131d6127c19b59234f9bf10022
|
data/README.md
CHANGED
@@ -2,11 +2,20 @@
|
|
2
2
|
|
3
3
|
[][rubygems]
|
4
4
|
[][circle]
|
5
|
+
[][climate]
|
5
6
|
|
6
7
|
[rubygems]: https://rubygems.org/gems/pg_party
|
7
8
|
[circle]: https://circleci.com/gh/rkrage/pg_party
|
9
|
+
[climate]: https://codeclimate.com/github/rkrage/pg_party
|
8
10
|
|
9
|
-
Active Record migrations and model helpers for creating and managing PostgreSQL 10 partitions!
|
11
|
+
[Active Record](http://guides.rubyonrails.org/active_record_basics.html) migrations and model helpers for creating and managing [PostgreSQL 10 partitions](https://www.postgresql.org/docs/10/static/ddl-partitioning.html)!
|
12
|
+
|
13
|
+
Features:
|
14
|
+
- migration methods for partition specific database operations
|
15
|
+
- model methods for querying partitioned data
|
16
|
+
- model methods for creating adhoc partitions
|
17
|
+
|
18
|
+
Note: PostgreSQL 10 is currently in beta. This gem should not be used in production environments (yet).
|
10
19
|
|
11
20
|
## Installation
|
12
21
|
|
@@ -26,11 +35,176 @@ Or install it yourself as:
|
|
26
35
|
|
27
36
|
## Usage
|
28
37
|
|
29
|
-
|
38
|
+
Full API documentation is in progress.
|
39
|
+
|
40
|
+
In the meantime, take a look at the [Combustion](https://github.com/pat/combustion) schema definition and integration specs:
|
41
|
+
- https://github.com/rkrage/pg_party/blob/master/spec/internal/db/schema.rb
|
42
|
+
- https://github.com/rkrage/pg_party/tree/master/spec/integration
|
43
|
+
|
44
|
+
### Migration Examples
|
45
|
+
|
46
|
+
Create range partition on `created_at::date` with two child partitions:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class CreateSomeRangeRecord < ActiveRecord::Migration[5.1]
|
50
|
+
def up
|
51
|
+
current_date = Date.current
|
52
|
+
|
53
|
+
create_range_partition :some_range_records, partition_key: "created_at::date" do |t|
|
54
|
+
t.text :some_value
|
55
|
+
t.timestamps
|
56
|
+
end
|
57
|
+
|
58
|
+
create_range_partition_of \
|
59
|
+
:some_range_records,
|
60
|
+
partition_key: "created_at::date",
|
61
|
+
start_range: current_date,
|
62
|
+
end_range: current_date + 1.day
|
63
|
+
|
64
|
+
create_range_partition_of \
|
65
|
+
:some_range_records,
|
66
|
+
partition_key: "created_at::date",
|
67
|
+
start_range: current_date + 1.day,
|
68
|
+
end_range: current_date + 2.days
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
Create list partition on `id` with two child partitions:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class CreateSomeListRecord < ActiveRecord::Migration[5.1]
|
77
|
+
def up
|
78
|
+
create_list_partition :some_list_records, partition_key: :id do |t|
|
79
|
+
t.text :some_value
|
80
|
+
t.timestamps
|
81
|
+
end
|
82
|
+
|
83
|
+
create_list_partition_of \
|
84
|
+
:some_list_records,
|
85
|
+
partition_key: :id,
|
86
|
+
values: (1..100).to_a
|
87
|
+
|
88
|
+
create_list_partition_of \
|
89
|
+
:some_list_records,
|
90
|
+
partition_key: :id,
|
91
|
+
values: (100..200).to_a
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
Attach an existing table to a range partition:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
class AttachRangePartition < ActiveRecord::Migration[5.1]
|
100
|
+
def up
|
101
|
+
attach_range_partition("parent_table", "child_table")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
Attach an existing table to a list partition:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class AttachListPartition < ActiveRecord::Migration[5.1]
|
110
|
+
def up
|
111
|
+
attach_list_partition("parent_table", "child_table")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
Detach a child table from any partition:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class DetachPartition < ActiveRecord::Migration[5.1]
|
120
|
+
def up
|
121
|
+
detach_partition("parent_table", "child_table")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
### Model Examples
|
127
|
+
|
128
|
+
Define model that is backed by a range partition:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class SomeRangeRecord < ApplicationRecord
|
132
|
+
range_partition_by "created_at::date"
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
Define model that is backed by a list partition:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
class SomeListRecord < ApplicationRecord
|
140
|
+
list_partition_by :id
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
Create child partition from range partition model:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
current_date = Date.current
|
148
|
+
|
149
|
+
SomeRangeRecord.create_partition(start_range: current_date + 1.day, end_range: current_date + 2.days)
|
150
|
+
```
|
151
|
+
|
152
|
+
Create child partition from list partition model:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
SomeListRecord.create_partition(values: (200..300).to_a)
|
156
|
+
```
|
157
|
+
|
158
|
+
Query for records within partition range:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
SomeRangeRecord.partition_key_in("2017-01-01".to_date, "2017-02-01".to_date)
|
162
|
+
```
|
163
|
+
|
164
|
+
Query for records in partition list:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
SomeListRecord.partition_key_in(1, 2, 3, 4)
|
168
|
+
```
|
169
|
+
|
170
|
+
Query for records matching partition key:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
SomeRangeRecord.partition_key_eq(Date.current)
|
174
|
+
|
175
|
+
SomeListRecord.partition_key_eq(100)
|
176
|
+
```
|
177
|
+
|
178
|
+
Query for records by partition name:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
# returns a collection of anonymous ActiveRecord::Base subclassed instances
|
182
|
+
SomeRangeRecord.in_partition("some_range_records_partition_name")
|
183
|
+
|
184
|
+
# returns a collection of anonymous ActiveRecord::Base subclassed instances
|
185
|
+
SomeListRecord.in_partition("some_list_records_partition_name")
|
186
|
+
```
|
30
187
|
|
31
188
|
## Development
|
32
189
|
|
33
|
-
|
190
|
+
The development / test environment relies heavily on [Docker](https://docs.docker.com).
|
191
|
+
|
192
|
+
Start the containers in the background:
|
193
|
+
|
194
|
+
$ docker-compose up -d
|
195
|
+
|
196
|
+
Install dependencies:
|
197
|
+
|
198
|
+
$ bin/de bundle
|
199
|
+
$ bin/de appraisal
|
200
|
+
|
201
|
+
Run the tests:
|
202
|
+
|
203
|
+
$ bin/de rake
|
204
|
+
|
205
|
+
Open a Pry console to play around with the sample Rails app:
|
206
|
+
|
207
|
+
$ bin/de console
|
34
208
|
|
35
209
|
## Contributing
|
36
210
|
|
@@ -1,31 +1,31 @@
|
|
1
1
|
module PgParty
|
2
|
-
module
|
3
|
-
module
|
4
|
-
def create_range_partition(*
|
2
|
+
module Adapter
|
3
|
+
module AbstractMethods
|
4
|
+
def create_range_partition(*)
|
5
5
|
raise NotImplementedError, "#create_range_partition is not implemented"
|
6
6
|
end
|
7
7
|
|
8
|
-
def create_list_partition(*
|
8
|
+
def create_list_partition(*)
|
9
9
|
raise NotImplementedError, "#create_list_partition is not implemented"
|
10
10
|
end
|
11
11
|
|
12
|
-
def create_range_partition_of(*
|
12
|
+
def create_range_partition_of(*)
|
13
13
|
raise NotImplementedError, "#create_range_partition_of is not implemented"
|
14
14
|
end
|
15
15
|
|
16
|
-
def create_list_partition_of(*
|
16
|
+
def create_list_partition_of(*)
|
17
17
|
raise NotImplementedError, "#create_list_partition_of is not implemented"
|
18
18
|
end
|
19
19
|
|
20
|
-
def attach_range_partition(*
|
20
|
+
def attach_range_partition(*)
|
21
21
|
raise NotImplementedError, "#attach_range_partition is not implemented"
|
22
22
|
end
|
23
23
|
|
24
|
-
def attach_list_partition(*
|
24
|
+
def attach_list_partition(*)
|
25
25
|
raise NotImplementedError, "#attach_list_partition is not implemented"
|
26
26
|
end
|
27
27
|
|
28
|
-
def detach_partition(*
|
28
|
+
def detach_partition(*)
|
29
29
|
raise NotImplementedError, "#detach_partition is not implemented"
|
30
30
|
end
|
31
31
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "pg_party/adapter_decorator"
|
2
|
+
|
3
|
+
module PgParty
|
4
|
+
module Adapter
|
5
|
+
module PostgreSQLMethods
|
6
|
+
def create_range_partition(*args, &blk)
|
7
|
+
PgParty::AdapterDecorator.new(self).create_range_partition(*args, &blk)
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_list_partition(*args, &blk)
|
11
|
+
PgParty::AdapterDecorator.new(self).create_list_partition(*args, &blk)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_range_partition_of(*args)
|
15
|
+
PgParty::AdapterDecorator.new(self).create_range_partition_of(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_list_partition_of(*args)
|
19
|
+
PgParty::AdapterDecorator.new(self).create_list_partition_of(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def attach_range_partition(*args)
|
23
|
+
PgParty::AdapterDecorator.new(self).attach_range_partition(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def attach_list_partition(*args)
|
27
|
+
PgParty::AdapterDecorator.new(self).attach_list_partition(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def detach_partition(*args)
|
31
|
+
PgParty::AdapterDecorator.new(self).detach_partition(*args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require "digest"
|
2
|
+
|
3
|
+
module PgParty
|
4
|
+
class AdapterDecorator < SimpleDelegator
|
5
|
+
def initialize(adapter)
|
6
|
+
super(adapter)
|
7
|
+
|
8
|
+
raise "Partitioning only supported in PostgreSQL >= 10.0" unless supports_partitions?
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_range_partition(table_name, partition_key:, **options, &blk)
|
12
|
+
create_partition(table_name, :range, partition_key, **options, &blk)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_list_partition(table_name, partition_key:, **options, &blk)
|
16
|
+
create_partition(table_name, :list, partition_key, **options, &blk)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_range_partition_of(table_name, start_range:, end_range:, **options)
|
20
|
+
if options[:name]
|
21
|
+
child_table_name = options[:name]
|
22
|
+
else
|
23
|
+
child_table_name = hashed_table_name(table_name, "#{start_range}#{end_range}")
|
24
|
+
end
|
25
|
+
|
26
|
+
constraint_clause = "FROM (#{quote(start_range)}) TO (#{quote(end_range)})"
|
27
|
+
|
28
|
+
create_partition_of(table_name, child_table_name, constraint_clause, **options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_list_partition_of(table_name, values:, **options)
|
32
|
+
if options[:name]
|
33
|
+
child_table_name = options[:name]
|
34
|
+
else
|
35
|
+
child_table_name = hashed_table_name(table_name, values.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
constraint_clause = "IN (#{Array.wrap(values).map(&method(:quote)).join(",")})"
|
39
|
+
|
40
|
+
create_partition_of(table_name, child_table_name, constraint_clause, **options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def attach_range_partition(parent_table_name, child_table_name, start_range:, end_range:)
|
44
|
+
execute(<<-SQL)
|
45
|
+
ALTER TABLE #{quote_table_name(parent_table_name)}
|
46
|
+
ATTACH PARTITION #{quote_table_name(child_table_name)}
|
47
|
+
FOR VALUES FROM (#{quote(start_range)}) TO (#{quote(end_range)})
|
48
|
+
SQL
|
49
|
+
end
|
50
|
+
|
51
|
+
def attach_list_partition(parent_table_name, child_table_name, values:)
|
52
|
+
execute(<<-SQL)
|
53
|
+
ALTER TABLE #{quote_table_name(parent_table_name)}
|
54
|
+
ATTACH PARTITION #{quote_table_name(child_table_name)}
|
55
|
+
FOR VALUES IN (#{Array.wrap(values).map(&method(:quote)).join(",")})
|
56
|
+
SQL
|
57
|
+
end
|
58
|
+
|
59
|
+
def detach_partition(parent_table_name, child_table_name)
|
60
|
+
execute(<<-SQL)
|
61
|
+
ALTER TABLE #{quote_table_name(parent_table_name)}
|
62
|
+
DETACH PARTITION #{quote_table_name(child_table_name)}
|
63
|
+
SQL
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def create_partition(table_name, type, partition_key, **options)
|
69
|
+
modified_options = options.except(:id, :primary_key)
|
70
|
+
id = options.fetch(:id, :bigserial)
|
71
|
+
primary_key = options.fetch(:primary_key, :id)
|
72
|
+
|
73
|
+
raise ArgumentError, "composite primary key not supported" if primary_key.is_a?(Array)
|
74
|
+
|
75
|
+
modified_options[:id] = false
|
76
|
+
modified_options[:options] = "PARTITION BY #{type.to_s.upcase} ((#{quote_partition_key(partition_key)}))"
|
77
|
+
|
78
|
+
create_table(table_name, modified_options) do |td|
|
79
|
+
if id == :uuid
|
80
|
+
td.send(id, primary_key, null: false, default: uuid_function)
|
81
|
+
elsif id
|
82
|
+
td.send(id, primary_key, null: false)
|
83
|
+
end
|
84
|
+
|
85
|
+
yield(td) if block_given?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_partition_of(table_name, child_table_name, constraint_clause, **options)
|
90
|
+
primary_key = options.fetch(:primary_key, :id)
|
91
|
+
index = options.fetch(:index, true)
|
92
|
+
partition_key = options[:partition_key]
|
93
|
+
|
94
|
+
raise ArgumentError, "composite primary key not supported" if primary_key.is_a?(Array)
|
95
|
+
|
96
|
+
partition_clause = <<-SQL
|
97
|
+
PARTITION OF #{quote_table_name(table_name)}
|
98
|
+
FOR VALUES #{constraint_clause}
|
99
|
+
SQL
|
100
|
+
|
101
|
+
create_table(child_table_name, id: false, options: partition_clause)
|
102
|
+
|
103
|
+
if primary_key
|
104
|
+
execute(<<-SQL)
|
105
|
+
ALTER TABLE #{quote_table_name(child_table_name)}
|
106
|
+
ADD PRIMARY KEY (#{quote_column_name(primary_key)})
|
107
|
+
SQL
|
108
|
+
end
|
109
|
+
|
110
|
+
if index && partition_key && primary_key != partition_key
|
111
|
+
add_index(child_table_name, "((#{quote_partition_key(partition_key)}))")
|
112
|
+
end
|
113
|
+
|
114
|
+
child_table_name
|
115
|
+
end
|
116
|
+
|
117
|
+
def quote_partition_key(key)
|
118
|
+
key.to_s.split("::").map(&method(:quote_column_name)).join("::")
|
119
|
+
end
|
120
|
+
|
121
|
+
def uuid_function
|
122
|
+
try(:supports_pgcrypto_uuid?) ? "gen_random_uuid()" : "uuid_generate_v4()"
|
123
|
+
end
|
124
|
+
|
125
|
+
def hashed_table_name(table_name, key)
|
126
|
+
"#{table_name}_#{Digest::MD5.hexdigest(key)[0..6]}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def supports_partitions?
|
130
|
+
postgresql_version >= 100000
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "pg_party/model_decorator"
|
2
|
+
|
3
|
+
module PgParty
|
4
|
+
module Model
|
5
|
+
module ListMethods
|
6
|
+
def create_partition(*args)
|
7
|
+
PgParty::ModelDecorator.new(self).create_list_partition(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def in_partition(*args)
|
11
|
+
PgParty::ModelDecorator.new(self).in_partition(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def partition_key_in(*args)
|
15
|
+
PgParty::ModelDecorator.new(self).list_partition_key_in(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def partition_key_eq(*args)
|
19
|
+
PgParty::ModelDecorator.new(self).partition_key_eq(*args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "pg_party/model_injector"
|
2
|
+
|
3
|
+
module PgParty
|
4
|
+
module Model
|
5
|
+
module Methods
|
6
|
+
def range_partition_by(key)
|
7
|
+
PgParty::ModelInjector.new(self, key).inject_range_methods
|
8
|
+
end
|
9
|
+
|
10
|
+
def list_partition_by(key)
|
11
|
+
PgParty::ModelInjector.new(self, key).inject_list_methods
|
12
|
+
end
|
13
|
+
|
14
|
+
def partitioned?
|
15
|
+
try(:partition_key).present?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "pg_party/model_decorator"
|
2
|
+
|
3
|
+
module PgParty
|
4
|
+
module Model
|
5
|
+
module RangeMethods
|
6
|
+
def create_partition(*args)
|
7
|
+
PgParty::ModelDecorator.new(self).create_range_partition(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def in_partition(*args)
|
11
|
+
PgParty::ModelDecorator.new(self).in_partition(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def partition_key_in(*args)
|
15
|
+
PgParty::ModelDecorator.new(self).range_partition_key_in(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def partition_key_eq(*args)
|
19
|
+
PgParty::ModelDecorator.new(self).partition_key_eq(*args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module PgParty
|
2
|
+
class ModelDecorator < SimpleDelegator
|
3
|
+
def in_partition(table_name)
|
4
|
+
child_class(table_name).all
|
5
|
+
end
|
6
|
+
|
7
|
+
def partition_key_eq(value)
|
8
|
+
where(partition_key_as_arel.eq(value))
|
9
|
+
end
|
10
|
+
|
11
|
+
def range_partition_key_in(start_range, end_range)
|
12
|
+
node = partition_key_as_arel
|
13
|
+
|
14
|
+
where(node.gteq(start_range).and(node.lt(end_range)))
|
15
|
+
end
|
16
|
+
|
17
|
+
def list_partition_key_in(*values)
|
18
|
+
where(partition_key_as_arel.in(values.flatten))
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_range_partition(start_range:, end_range:, **options)
|
22
|
+
modified_options = options.merge(
|
23
|
+
start_range: start_range,
|
24
|
+
end_range: end_range,
|
25
|
+
primary_key: primary_key,
|
26
|
+
partition_key: partition_key
|
27
|
+
)
|
28
|
+
|
29
|
+
connection.create_range_partition_of(table_name, **modified_options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_list_partition(values:, **options)
|
33
|
+
modified_options = options.merge(
|
34
|
+
values: values,
|
35
|
+
primary_key: primary_key,
|
36
|
+
partition_key: partition_key
|
37
|
+
)
|
38
|
+
|
39
|
+
connection.create_list_partition_of(table_name, **modified_options)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def child_class(table_name)
|
45
|
+
Class.new(__getobj__) do
|
46
|
+
self.table_name = table_name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def partition_key_as_arel
|
51
|
+
arel_column = arel_table[partition_column]
|
52
|
+
|
53
|
+
if partition_cast
|
54
|
+
quoted_cast = connection.quote_column_name(partition_cast)
|
55
|
+
|
56
|
+
Arel::Nodes::NamedFunction.new("CAST", [arel_column.as(quoted_cast)])
|
57
|
+
else
|
58
|
+
arel_column
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module PgParty
|
2
|
+
class ModelInjector
|
3
|
+
def initialize(model, key)
|
4
|
+
@model = model
|
5
|
+
@key = key
|
6
|
+
|
7
|
+
@column, @cast = key.to_s.split("::")
|
8
|
+
end
|
9
|
+
|
10
|
+
def inject_range_methods
|
11
|
+
create_class_attributes
|
12
|
+
|
13
|
+
require "pg_party/model/range_methods"
|
14
|
+
@model.extend(PgParty::Model::RangeMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inject_list_methods
|
18
|
+
create_class_attributes
|
19
|
+
|
20
|
+
require "pg_party/model/list_methods"
|
21
|
+
@model.extend(PgParty::Model::ListMethods)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def create_class_attributes
|
27
|
+
@model.class_attribute(
|
28
|
+
:partition_key,
|
29
|
+
:partition_column,
|
30
|
+
:partition_cast,
|
31
|
+
instance_accessor: false
|
32
|
+
)
|
33
|
+
|
34
|
+
@model.partition_key = @key
|
35
|
+
@model.partition_column = @column
|
36
|
+
@model.partition_cast = @cast
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/pg_party/version.rb
CHANGED
data/lib/pg_party.rb
CHANGED
@@ -2,15 +2,20 @@ require "pg_party/version"
|
|
2
2
|
require "active_support"
|
3
3
|
|
4
4
|
ActiveSupport.on_load(:active_record) do
|
5
|
-
require "pg_party/
|
6
|
-
require "pg_party/model_methods"
|
5
|
+
require "pg_party/model/methods"
|
7
6
|
|
8
|
-
extend PgParty::
|
9
|
-
extend PgParty::ModelMethods
|
7
|
+
extend PgParty::Model::Methods
|
10
8
|
|
11
|
-
require "pg_party/
|
9
|
+
require "pg_party/adapter/abstract_methods"
|
12
10
|
|
13
11
|
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
14
|
-
include PgParty::
|
12
|
+
include PgParty::Adapter::AbstractMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
require "pg_party/adapter/postgresql_methods"
|
16
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
17
|
+
|
18
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
19
|
+
include PgParty::Adapter::PostgreSQLMethods
|
15
20
|
end
|
16
21
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_party
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Krage
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-09-
|
11
|
+
date: 2017-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -189,13 +189,14 @@ files:
|
|
189
189
|
- LICENSE.txt
|
190
190
|
- README.md
|
191
191
|
- lib/pg_party.rb
|
192
|
-
- lib/pg_party/
|
193
|
-
- lib/pg_party/
|
194
|
-
- lib/pg_party/
|
195
|
-
- lib/pg_party/
|
196
|
-
- lib/pg_party/
|
197
|
-
- lib/pg_party/
|
198
|
-
- lib/pg_party/
|
192
|
+
- lib/pg_party/adapter/abstract_methods.rb
|
193
|
+
- lib/pg_party/adapter/postgresql_methods.rb
|
194
|
+
- lib/pg_party/adapter_decorator.rb
|
195
|
+
- lib/pg_party/model/list_methods.rb
|
196
|
+
- lib/pg_party/model/methods.rb
|
197
|
+
- lib/pg_party/model/range_methods.rb
|
198
|
+
- lib/pg_party/model_decorator.rb
|
199
|
+
- lib/pg_party/model_injector.rb
|
199
200
|
- lib/pg_party/version.rb
|
200
201
|
homepage: https://github.com/rkrage/pg_party
|
201
202
|
licenses:
|
@@ -217,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
217
218
|
version: 1.8.11
|
218
219
|
requirements: []
|
219
220
|
rubyforge_project:
|
220
|
-
rubygems_version: 2.
|
221
|
+
rubygems_version: 2.6.11
|
221
222
|
signing_key:
|
222
223
|
specification_version: 4
|
223
224
|
summary: ActiveRecord PostgreSQL Partitioning
|
@@ -1,125 +0,0 @@
|
|
1
|
-
require "digest"
|
2
|
-
|
3
|
-
module PgParty
|
4
|
-
module ConnectionAdapters
|
5
|
-
module PostgreSQLAdapter
|
6
|
-
def create_range_partition(table_name, partition_key:, **options, &blk)
|
7
|
-
create_partition(table_name, :range, partition_key, **options, &blk)
|
8
|
-
end
|
9
|
-
|
10
|
-
def create_list_partition(table_name, partition_key:, **options, &blk)
|
11
|
-
create_partition(table_name, :list, partition_key, **options, &blk)
|
12
|
-
end
|
13
|
-
|
14
|
-
def create_range_partition_of(table_name, start_range:, end_range:, **options)
|
15
|
-
if options[:name]
|
16
|
-
child_table_name = options[:name]
|
17
|
-
else
|
18
|
-
child_table_name = hashed_table_name(table_name, "#{start_range}#{end_range}")
|
19
|
-
end
|
20
|
-
|
21
|
-
constraint_clause = "FROM (#{quote(start_range)}) TO (#{quote(end_range)})"
|
22
|
-
|
23
|
-
create_partition_of(table_name, child_table_name, constraint_clause, **options)
|
24
|
-
end
|
25
|
-
|
26
|
-
def create_list_partition_of(table_name, values:, **options)
|
27
|
-
if options[:name]
|
28
|
-
child_table_name = options[:name]
|
29
|
-
else
|
30
|
-
child_table_name = hashed_table_name(table_name, values.to_s)
|
31
|
-
end
|
32
|
-
|
33
|
-
constraint_clause = "IN (#{Array.wrap(values).map(&method(:quote)).join(",")})"
|
34
|
-
|
35
|
-
create_partition_of(table_name, child_table_name, constraint_clause, **options)
|
36
|
-
end
|
37
|
-
|
38
|
-
def attach_range_partition(parent_table_name, child_table_name, start_range:, end_range:)
|
39
|
-
execute(<<-SQL)
|
40
|
-
ALTER TABLE #{quote_table_name(parent_table_name)}
|
41
|
-
ATTACH PARTITION #{quote_table_name(child_table_name)}
|
42
|
-
FOR VALUES FROM (#{quote(start_range)}) TO (#{quote(end_range)})
|
43
|
-
SQL
|
44
|
-
end
|
45
|
-
|
46
|
-
def attach_list_partition(parent_table_name, child_table_name, values:)
|
47
|
-
execute(<<-SQL)
|
48
|
-
ALTER TABLE #{quote_table_name(parent_table_name)}
|
49
|
-
ATTACH PARTITION #{quote_table_name(child_table_name)}
|
50
|
-
FOR VALUES IN (#{Array.wrap(values).map(&method(:quote)).join(",")})
|
51
|
-
SQL
|
52
|
-
end
|
53
|
-
|
54
|
-
def detach_partition(parent_table_name, child_table_name)
|
55
|
-
execute(<<-SQL)
|
56
|
-
ALTER TABLE #{quote_table_name(parent_table_name)}
|
57
|
-
DETACH PARTITION #{quote_table_name(child_table_name)}
|
58
|
-
SQL
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def create_partition(table_name, type, partition_key, **options)
|
64
|
-
modified_options = options.except(:id, :primary_key)
|
65
|
-
id = options.fetch(:id, :bigserial)
|
66
|
-
primary_key = options.fetch(:primary_key, :id)
|
67
|
-
|
68
|
-
raise ArgumentError, "composite primary key not supported" if primary_key.is_a?(Array)
|
69
|
-
|
70
|
-
modified_options[:id] = false
|
71
|
-
modified_options[:options] = "PARTITION BY #{type.to_s.upcase} ((#{quote_partition_key(partition_key)}))"
|
72
|
-
|
73
|
-
create_table(table_name, modified_options) do |td|
|
74
|
-
if id == :uuid
|
75
|
-
td.send(id, primary_key, null: false, default: uuid_function)
|
76
|
-
elsif id
|
77
|
-
td.send(id, primary_key, null: false)
|
78
|
-
end
|
79
|
-
|
80
|
-
yield td if block_given?
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def create_partition_of(table_name, child_table_name, constraint_clause, **options)
|
85
|
-
primary_key = options.fetch(:primary_key, :id)
|
86
|
-
index = options.fetch(:index, true)
|
87
|
-
partition_key = options[:partition_key]
|
88
|
-
|
89
|
-
raise ArgumentError, "composite primary key not supported" if primary_key.is_a?(Array)
|
90
|
-
|
91
|
-
partition_clause = <<-SQL
|
92
|
-
PARTITION OF #{quote_table_name(table_name)}
|
93
|
-
FOR VALUES #{constraint_clause}
|
94
|
-
SQL
|
95
|
-
|
96
|
-
create_table(child_table_name, id: false, options: partition_clause)
|
97
|
-
|
98
|
-
if primary_key
|
99
|
-
execute(<<-SQL)
|
100
|
-
ALTER TABLE #{quote_table_name(child_table_name)}
|
101
|
-
ADD PRIMARY KEY (#{quote_column_name(primary_key)})
|
102
|
-
SQL
|
103
|
-
end
|
104
|
-
|
105
|
-
if index && partition_key && primary_key != partition_key
|
106
|
-
add_index(child_table_name, "((#{quote_partition_key(partition_key)}))")
|
107
|
-
end
|
108
|
-
|
109
|
-
child_table_name
|
110
|
-
end
|
111
|
-
|
112
|
-
def quote_partition_key(key)
|
113
|
-
key.to_s.split("::").map(&method(:quote_column_name)).join("::")
|
114
|
-
end
|
115
|
-
|
116
|
-
def uuid_function
|
117
|
-
try(:supports_pgcrypto_uuid?) ? "gen_random_uuid()" : "uuid_generate_v4()"
|
118
|
-
end
|
119
|
-
|
120
|
-
def hashed_table_name(table_name, key)
|
121
|
-
"#{table_name}_#{Digest::MD5.hexdigest(key)[0..6]}"
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module PgParty
|
2
|
-
module ConnectionHandling
|
3
|
-
def establish_connection(*args)
|
4
|
-
super.tap do
|
5
|
-
if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
6
|
-
require "pg_party/connection_adapters/postgresql_adapter"
|
7
|
-
|
8
|
-
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
9
|
-
include PgParty::ConnectionAdapters::PostgreSQLAdapter
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require "pg_party/injected_model_methods"
|
2
|
-
|
3
|
-
module PgParty
|
4
|
-
module InjectedListModelMethods
|
5
|
-
include InjectedModelMethods
|
6
|
-
|
7
|
-
def create_partition(values:, **options)
|
8
|
-
modified_options = options.merge(
|
9
|
-
values: values,
|
10
|
-
primary_key: primary_key,
|
11
|
-
partition_key: partition_key
|
12
|
-
)
|
13
|
-
|
14
|
-
connection.create_list_partition_of(table_name, **modified_options)
|
15
|
-
end
|
16
|
-
|
17
|
-
def partition_key_in(*values)
|
18
|
-
where(partition_key_as_arel.in(values.flatten))
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module PgParty
|
2
|
-
module InjectedModelMethods
|
3
|
-
attr_reader :partition_key, :partition_column, :partition_cast
|
4
|
-
|
5
|
-
def partition_key_matching(value)
|
6
|
-
where(partition_key_as_arel.eq(value))
|
7
|
-
end
|
8
|
-
|
9
|
-
private
|
10
|
-
|
11
|
-
def partition_key_as_arel
|
12
|
-
arel_column = arel_table[partition_column]
|
13
|
-
|
14
|
-
if partition_cast
|
15
|
-
quoted_cast = connection.quote_column_name(partition_cast)
|
16
|
-
|
17
|
-
Arel::Nodes::NamedFunction.new("CAST", [arel_column.as(quoted_cast)])
|
18
|
-
else
|
19
|
-
arel_column
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require "pg_party/injected_model_methods"
|
2
|
-
|
3
|
-
module PgParty
|
4
|
-
module InjectedRangeModelMethods
|
5
|
-
include InjectedModelMethods
|
6
|
-
|
7
|
-
def create_partition(start_range:, end_range:, **options)
|
8
|
-
modified_options = options.merge(
|
9
|
-
start_range: start_range,
|
10
|
-
end_range: end_range,
|
11
|
-
primary_key: primary_key,
|
12
|
-
partition_key: partition_key
|
13
|
-
)
|
14
|
-
|
15
|
-
connection.create_range_partition_of(table_name, **modified_options)
|
16
|
-
end
|
17
|
-
|
18
|
-
def partition_key_in(start_range, end_range)
|
19
|
-
node = partition_key_as_arel
|
20
|
-
|
21
|
-
where(node.gteq(start_range).and(node.lt(end_range)))
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module PgParty
|
2
|
-
module ModelMethods
|
3
|
-
def range_partition_by(key)
|
4
|
-
@partition_key = key
|
5
|
-
@partition_column, @partition_cast = key.to_s.split("::")
|
6
|
-
|
7
|
-
require "pg_party/injected_range_model_methods"
|
8
|
-
extend InjectedRangeModelMethods
|
9
|
-
end
|
10
|
-
|
11
|
-
def list_partition_by(key)
|
12
|
-
@partition_key = key
|
13
|
-
@partition_column, @partition_cast = key.to_s.split("::")
|
14
|
-
|
15
|
-
require "pg_party/injected_list_model_methods"
|
16
|
-
extend InjectedListModelMethods
|
17
|
-
end
|
18
|
-
|
19
|
-
def partitioned?
|
20
|
-
@partition_key.present?
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|