pg_party 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/pg_party.svg)][rubygems]
|
4
4
|
[![Build Status](https://circleci.com/gh/rkrage/pg_party.svg?&style=shield)][circle]
|
5
|
+
[![Code Climate](https://codeclimate.com/github/rkrage/pg_party/badges/gpa.svg)][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
|