pg_party 0.7.3 → 1.0.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 +5 -5
- data/README.md +194 -64
- data/lib/pg_party.rb +15 -7
- data/lib/pg_party/adapter/abstract_methods.rb +13 -7
- data/lib/pg_party/adapter/postgresql_methods.rb +6 -0
- data/lib/pg_party/adapter_decorator.rb +81 -65
- data/lib/pg_party/cache.rb +2 -0
- data/lib/pg_party/hacks/schema_cache.rb +13 -0
- data/lib/pg_party/model/list_methods.rb +2 -0
- data/lib/pg_party/model/methods.rb +6 -4
- data/lib/pg_party/model/range_methods.rb +2 -0
- data/lib/pg_party/model/shared_methods.rb +2 -0
- data/lib/pg_party/model_decorator.rb +53 -20
- data/lib/pg_party/model_injector.rb +10 -7
- data/lib/pg_party/version.rb +3 -1
- metadata +50 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b3eb42aaee8d1f94ffd962760c6f4596330cbdc899a314738848861bfc01e29c
|
4
|
+
data.tar.gz: '049172b0bb51a0d8664ad54b4599a588c448b8558861c3f50688fb938aaf7680'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e00484e8d7142cdff81b64301f5cf2207318e07dd9f918730472ece7840e2025e71e15a827987042c3ede9c66ad41633af88cc4ec7e0f83d7a4e28c57e822a8
|
7
|
+
data.tar.gz: c3fa8f87087bd47e87910fcb2707cd58d83c7e6bf91207dfde299b550c9aace44cc905a7781d9ddc5b4281a8de654bf096813420aa8937442bf09a053f13f549
|
data/README.md
CHANGED
@@ -2,26 +2,28 @@
|
|
2
2
|
|
3
3
|
[][rubygems]
|
4
4
|
[][circle]
|
5
|
-
[][gemnasium]
|
6
5
|
[][cc_maintainability]
|
7
6
|
[][cc_coverage]
|
8
7
|
|
9
8
|
[rubygems]: https://rubygems.org/gems/pg_party
|
10
9
|
[circle]: https://circleci.com/gh/rkrage/pg_party/tree/master
|
11
|
-
[gemnasium]: https://gemnasium.com/github.com/rkrage/pg_party
|
12
10
|
[cc_maintainability]: https://codeclimate.com/github/rkrage/pg_party/maintainability
|
13
11
|
[cc_coverage]: https://codeclimate.com/github/rkrage/pg_party/test_coverage
|
14
12
|
|
15
|
-
[ActiveRecord](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)!
|
13
|
+
[ActiveRecord](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)!
|
16
14
|
|
17
|
-
Features
|
18
|
-
- Migration methods for partition specific database operations
|
19
|
-
- Model methods for querying partitioned data
|
20
|
-
- Model methods for creating adhoc partitions
|
15
|
+
## Features
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
- Migration methods for partition specific database operations
|
18
|
+
- Model methods for querying partitioned data, creating adhoc partitions, and retreiving partition metadata
|
19
|
+
|
20
|
+
## Limitations
|
21
|
+
|
22
|
+
- Partition tables are not represented correctly in `db/schema.rb` — please use the `:sql` schema format
|
23
|
+
|
24
|
+
## Future Work
|
25
|
+
|
26
|
+
- Automatic partition creation (via cron or some other means)
|
25
27
|
|
26
28
|
## Installation
|
27
29
|
|
@@ -33,89 +35,152 @@ gem 'pg_party'
|
|
33
35
|
|
34
36
|
And then execute:
|
35
37
|
|
36
|
-
|
38
|
+
```
|
39
|
+
$ bundle
|
40
|
+
```
|
37
41
|
|
38
42
|
Or install it yourself as:
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
Full API documentation is in progress.
|
44
|
+
```
|
45
|
+
$ gem install pg_party
|
46
|
+
```
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
- https://github.com/rkrage/pg_party/tree/master/spec/integration
|
48
|
+
Note that the gemspec does not require `pg`, as some model methods _may_ work for other databases.
|
49
|
+
Migration methods will be unavailable unless `pg` is installed.
|
49
50
|
|
50
|
-
|
51
|
+
## Usage
|
51
52
|
|
52
|
-
|
53
|
+
### Migrations
|
54
|
+
|
55
|
+
#### Methods
|
56
|
+
|
57
|
+
These methods are available in migrations as well as `ActiveRecord::Base#connection` objects.
|
58
|
+
|
59
|
+
- `create_range_partition`
|
60
|
+
- Create partitioned table using the _range_ partitioning method
|
61
|
+
- Required args: `table_name`, `partitition_key:`
|
62
|
+
- `create_list_partition`
|
63
|
+
- Create partitioned table using the _list_ partitioning method
|
64
|
+
- Required args: `table_name`, `partition_key:`
|
65
|
+
- `create_range_partition_of`
|
66
|
+
- Create partition in _range_ partitioned table with partition key between _range_ of values
|
67
|
+
- Required args: `table_name`, `start_range:`, `end_range:`
|
68
|
+
- `create_list_partition_of`
|
69
|
+
- Create partition in _list_ partitioned table with partition key in _list_ of values
|
70
|
+
- Required args: `table_name`, `values:`
|
71
|
+
- `attach_range_partition`
|
72
|
+
- Attach existing table to _range_ partitioned table with partition key between _range_ of values
|
73
|
+
- Required args: `parent_table_name`, `child_table_name`, `start_range:`, `end_range:`
|
74
|
+
- `attach_list_partition`
|
75
|
+
- Attach existing table to _list_ partitioned table with partition key in _list_ of values
|
76
|
+
- Required args: `parent_table_name`, `child_table_name`, `values:`
|
77
|
+
- `detach_partition`
|
78
|
+
- Detach partition from both _range and list_ partitioned tables
|
79
|
+
- Required args: `parent_table_name`, `child_table_name`
|
80
|
+
- `create_table_like`
|
81
|
+
- Clone _any_ existing table
|
82
|
+
- Required args: `table_name`, `new_table_name`
|
83
|
+
|
84
|
+
#### Examples
|
85
|
+
|
86
|
+
Create _range_ partitioned table on `created_at::date` with two partitions:
|
53
87
|
|
54
88
|
```ruby
|
55
89
|
class CreateSomeRangeRecord < ActiveRecord::Migration[5.1]
|
56
90
|
def up
|
57
|
-
|
58
|
-
|
59
|
-
create_range_partition :some_range_records, partition_key: "created_at::date" do |t|
|
91
|
+
# proc is used for partition keys containing expressions
|
92
|
+
create_range_partition :some_range_records, partition_key: ->{ "(created_at::date)" } do |t|
|
60
93
|
t.text :some_value
|
61
94
|
t.timestamps
|
62
95
|
end
|
63
96
|
|
97
|
+
# optional name argument is used to specify child table name
|
64
98
|
create_range_partition_of \
|
65
99
|
:some_range_records,
|
66
|
-
|
67
|
-
start_range:
|
68
|
-
end_range:
|
100
|
+
name: :some_range_records_a,
|
101
|
+
start_range: "2019-06-07",
|
102
|
+
end_range: "2019-06-08"
|
69
103
|
|
104
|
+
# optional name argument is used to specify child table name
|
70
105
|
create_range_partition_of \
|
71
106
|
:some_range_records,
|
72
|
-
|
73
|
-
start_range:
|
74
|
-
end_range:
|
107
|
+
name: :some_range_records_b,
|
108
|
+
start_range: "2019-06-08",
|
109
|
+
end_range: "2019-06-09"
|
75
110
|
end
|
76
111
|
end
|
77
112
|
```
|
78
113
|
|
79
|
-
Create
|
114
|
+
Create _list_ partitioned table on `id` with two partitions:
|
80
115
|
|
81
116
|
```ruby
|
82
117
|
class CreateSomeListRecord < ActiveRecord::Migration[5.1]
|
83
118
|
def up
|
119
|
+
# symbol is used for partition keys referring to individual columns
|
84
120
|
create_list_partition :some_list_records, partition_key: :id do |t|
|
85
121
|
t.text :some_value
|
86
122
|
t.timestamps
|
87
123
|
end
|
88
124
|
|
125
|
+
# without name argument, child partition created as "some_list_records_<hash>"
|
89
126
|
create_list_partition_of \
|
90
127
|
:some_list_records,
|
91
|
-
|
92
|
-
values: (1..100).to_a
|
128
|
+
values: 1..100
|
93
129
|
|
130
|
+
# without name argument, child partition created as "some_list_records_<hash>"
|
94
131
|
create_list_partition_of \
|
95
132
|
:some_list_records,
|
96
|
-
|
97
|
-
values: (100..200).to_a
|
133
|
+
values: 101..200
|
98
134
|
end
|
99
135
|
end
|
100
136
|
```
|
101
137
|
|
102
|
-
|
138
|
+
Unfortunately, PostgreSQL 10 doesn't support indexes on partitioned tables.
|
139
|
+
However, individual _partitions_ can have indexes.
|
140
|
+
To avoid explicit index creation for _every_ new partition, we've introduced the idea of template tables.
|
141
|
+
For every call to `create_list_partition` and `create_range_partition`, a clone `<table_name>_template` is created.
|
142
|
+
Indexes, constraints, etc. created on the template table will propagate to new partitions in calls to `create_list_partition_of` and `create_range_partition_of`:
|
103
143
|
|
104
144
|
```ruby
|
105
|
-
class
|
145
|
+
class CreateSomeListRecord < ActiveRecord::Migration[5.1]
|
106
146
|
def up
|
107
|
-
|
147
|
+
# template table creation is enabled by default - use "template: false" to opt-out
|
148
|
+
create_list_partition :some_list_records, partition_key: :id do |t|
|
149
|
+
t.integer :some_foreign_id
|
150
|
+
t.text :some_value
|
151
|
+
t.timestamps
|
152
|
+
end
|
153
|
+
|
154
|
+
# create index on the template table
|
155
|
+
add_index :some_list_records_template, :some_foreign_id
|
156
|
+
|
157
|
+
# create partition with an index on "some_foreign_id"
|
158
|
+
create_list_partition_of \
|
159
|
+
:some_list_records,
|
160
|
+
values: 1..100
|
161
|
+
|
162
|
+
# create partition with an index on "some_foreign_id"
|
163
|
+
create_list_partition_of \
|
164
|
+
:some_list_records,
|
165
|
+
values: 101..200
|
166
|
+
end
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
Attach an existing table to a _range_ partitioned table:
|
108
171
|
|
172
|
+
```ruby
|
173
|
+
def up
|
109
174
|
attach_range_partition \
|
110
175
|
:some_range_records,
|
111
176
|
:some_existing_table,
|
112
|
-
start_range:
|
113
|
-
end_range:
|
177
|
+
start_range: "2019-06-09",
|
178
|
+
end_range: "2019-06-10"
|
114
179
|
end
|
115
180
|
end
|
116
181
|
```
|
117
182
|
|
118
|
-
Attach an existing table to a
|
183
|
+
Attach an existing table to a _list_ partitioned table:
|
119
184
|
|
120
185
|
```ruby
|
121
186
|
class AttachListPartition < ActiveRecord::Migration[5.1]
|
@@ -123,12 +188,12 @@ class AttachListPartition < ActiveRecord::Migration[5.1]
|
|
123
188
|
attach_list_partition \
|
124
189
|
:some_list_records,
|
125
190
|
:some_existing_table,
|
126
|
-
values:
|
191
|
+
values: 200..300
|
127
192
|
end
|
128
193
|
end
|
129
194
|
```
|
130
195
|
|
131
|
-
Detach a
|
196
|
+
Detach a partition from any partitioned table:
|
132
197
|
|
133
198
|
```ruby
|
134
199
|
class DetachPartition < ActiveRecord::Migration[5.1]
|
@@ -138,51 +203,104 @@ class DetachPartition < ActiveRecord::Migration[5.1]
|
|
138
203
|
end
|
139
204
|
```
|
140
205
|
|
141
|
-
|
206
|
+
For more examples, take a look at the Combustion schema definition and integration specs:
|
207
|
+
|
208
|
+
- https://github.com/rkrage/pg_party/blob/master/spec/dummy/db/schema.rb
|
209
|
+
- https://github.com/rkrage/pg_party/blob/master/spec/integration/migration_spec.rb
|
210
|
+
|
211
|
+
### Models
|
212
|
+
|
213
|
+
#### Methods
|
214
|
+
|
215
|
+
Class methods available to _all_ ActiveRecord models:
|
216
|
+
|
217
|
+
- `partitioned?`
|
218
|
+
- Check if a model is backed by either a _list or range_ partitioned table
|
219
|
+
- No arguments
|
220
|
+
- `range_partition_by`
|
221
|
+
- Configure a model backed by a _range_ partitioned table
|
222
|
+
- Required arg: `key` (partition key column) or block returning partition key expression
|
223
|
+
- `list_partition_by`
|
224
|
+
- Configure a model backed by a _list_ partitioned table
|
225
|
+
- Required arg: `key` (partition key column) or block returning partition key expression
|
226
|
+
|
227
|
+
Class methods available to both _range and list_ partitioned models:
|
228
|
+
|
229
|
+
- `partitions`
|
230
|
+
- Retrieve a list of currently attached partitions
|
231
|
+
- No arguments
|
232
|
+
- `in_partition`
|
233
|
+
- Retrieve an ActiveRecord model scoped to an individual partition
|
234
|
+
- Required arg: `child_table_name`
|
235
|
+
- `partition_key_eq`
|
236
|
+
- Query for records where partition key matches a value
|
237
|
+
- Required arg: `value`
|
238
|
+
|
239
|
+
Class methods available to _range_ partitioned models:
|
240
|
+
|
241
|
+
- `create_partition`
|
242
|
+
- Dynamically create new partition with partition key in _range_ of values
|
243
|
+
- Required args: `start_range:`, `end_range:`
|
244
|
+
- `partition_key_in`
|
245
|
+
- Query for records where partition key in _range_ of values
|
246
|
+
- Required args: `start_range`, `end_range`
|
247
|
+
|
248
|
+
Class methods available to _list_ partitioned models:
|
142
249
|
|
143
|
-
|
250
|
+
- `create_partition`
|
251
|
+
- Dynamically create new partition with partition key in _list_ of values
|
252
|
+
- Required arg: `values:`
|
253
|
+
- `partition_key_in`
|
254
|
+
- Query for records where partition key in _list_ of values
|
255
|
+
- Required arg: list of `values`
|
256
|
+
|
257
|
+
#### Examples
|
258
|
+
|
259
|
+
Configure model backed by a _range_ partitioned table to get access to the methods described above:
|
144
260
|
|
145
261
|
```ruby
|
146
262
|
class SomeRangeRecord < ApplicationRecord
|
147
|
-
|
263
|
+
# block is used for partition keys containing expressions
|
264
|
+
range_partition_by { "(created_at::date)" }
|
148
265
|
end
|
149
266
|
```
|
150
267
|
|
151
|
-
|
268
|
+
Configure model backed by a _list_ partitioned table to get access to the methods described above:
|
152
269
|
|
153
270
|
```ruby
|
154
271
|
class SomeListRecord < ApplicationRecord
|
272
|
+
# symbol is used for partition keys referring to individual columns
|
155
273
|
list_partition_by :id
|
156
274
|
end
|
157
275
|
```
|
158
276
|
|
159
|
-
|
277
|
+
Dynamically create new partition from _range_ partitioned model:
|
160
278
|
|
161
279
|
```ruby
|
162
|
-
|
163
|
-
|
164
|
-
SomeRangeRecord.create_partition(start_range: current_date + 1.day, end_range: current_date + 2.days)
|
280
|
+
# additional options include: "name:" and "primary_key:"
|
281
|
+
SomeRangeRecord.create_partition(start_range: "2019-06-09", end_range: "2019-06-10")
|
165
282
|
```
|
166
283
|
|
167
|
-
|
284
|
+
Dynamically create new partition from _list_ partitioned model:
|
168
285
|
|
169
286
|
```ruby
|
170
|
-
|
287
|
+
# additional options include: "name:" and "primary_key:"
|
288
|
+
SomeListRecord.create_partition(values: 200..300)
|
171
289
|
```
|
172
290
|
|
173
|
-
|
291
|
+
For _range_ partitioned model, query for records where partition key in _range_ of values:
|
174
292
|
|
175
293
|
```ruby
|
176
|
-
SomeRangeRecord.partition_key_in("
|
294
|
+
SomeRangeRecord.partition_key_in("2019-06-08", "2019-06-10")
|
177
295
|
```
|
178
296
|
|
179
|
-
|
297
|
+
For _list_ partitioned model, query for records where partition key in _list_ of values:
|
180
298
|
|
181
299
|
```ruby
|
182
300
|
SomeListRecord.partition_key_in(1, 2, 3, 4)
|
183
301
|
```
|
184
302
|
|
185
|
-
|
303
|
+
For both _range and list_ partitioned models, query for records matching partition key:
|
186
304
|
|
187
305
|
```ruby
|
188
306
|
SomeRangeRecord.partition_key_eq(Date.current)
|
@@ -190,7 +308,7 @@ SomeRangeRecord.partition_key_eq(Date.current)
|
|
190
308
|
SomeListRecord.partition_key_eq(100)
|
191
309
|
```
|
192
310
|
|
193
|
-
|
311
|
+
For both _range and list_ partitioned models, retrieve currently attached partitions:
|
194
312
|
|
195
313
|
```ruby
|
196
314
|
SomeRangeRecord.partitions
|
@@ -198,7 +316,7 @@ SomeRangeRecord.partitions
|
|
198
316
|
SomeListRecord.partitions
|
199
317
|
```
|
200
318
|
|
201
|
-
|
319
|
+
For both _range and list_ partitioned models, retrieve ActiveRecord model scoped to individual partition:
|
202
320
|
|
203
321
|
```ruby
|
204
322
|
SomeRangeRecord.in_partition(:some_range_records_partition_name)
|
@@ -206,26 +324,38 @@ SomeRangeRecord.in_partition(:some_range_records_partition_name)
|
|
206
324
|
SomeListRecord.in_partition(:some_list_records_partition_name)
|
207
325
|
```
|
208
326
|
|
327
|
+
For more examples, take a look at the model integration specs:
|
328
|
+
|
329
|
+
- https://github.com/rkrage/pg_party/tree/documentation/spec/integration/model
|
330
|
+
|
209
331
|
## Development
|
210
332
|
|
211
333
|
The development / test environment relies heavily on [Docker](https://docs.docker.com).
|
212
334
|
|
213
335
|
Start the containers in the background:
|
214
336
|
|
215
|
-
|
337
|
+
```
|
338
|
+
$ docker-compose up -d
|
339
|
+
```
|
216
340
|
|
217
341
|
Install dependencies:
|
218
342
|
|
219
|
-
|
220
|
-
|
343
|
+
```
|
344
|
+
$ bin/de bundle
|
345
|
+
$ bin/de appraisal
|
346
|
+
```
|
221
347
|
|
222
348
|
Run the tests:
|
223
349
|
|
224
|
-
|
350
|
+
```
|
351
|
+
$ bin/de appraisal rake
|
352
|
+
```
|
225
353
|
|
226
354
|
Open a Pry console to play around with the sample Rails app:
|
227
355
|
|
228
|
-
|
356
|
+
```
|
357
|
+
$ bin/de console
|
358
|
+
```
|
229
359
|
|
230
360
|
## Contributing
|
231
361
|
|
data/lib/pg_party.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pg_party/version"
|
2
4
|
require "active_support"
|
3
5
|
|
@@ -8,17 +10,23 @@ ActiveSupport.on_load(:active_record) do
|
|
8
10
|
|
9
11
|
require "pg_party/adapter/abstract_methods"
|
10
12
|
|
11
|
-
ActiveRecord::ConnectionAdapters::AbstractAdapter.
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.include(
|
14
|
+
PgParty::Adapter::AbstractMethods
|
15
|
+
)
|
16
|
+
|
17
|
+
require "pg_party/hacks/schema_cache"
|
18
|
+
|
19
|
+
ActiveRecord::ConnectionAdapters::SchemaCache.include(
|
20
|
+
PgParty::Hacks::SchemaCache
|
21
|
+
)
|
22
|
+
|
15
23
|
begin
|
16
24
|
require "active_record/connection_adapters/postgresql_adapter"
|
17
25
|
require "pg_party/adapter/postgresql_methods"
|
18
26
|
|
19
|
-
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
|
20
|
-
|
21
|
-
|
27
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.include(
|
28
|
+
PgParty::Adapter::PostgreSQLMethods
|
29
|
+
)
|
22
30
|
rescue LoadError
|
23
31
|
# migration methods will not be available
|
24
32
|
end
|
@@ -1,32 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PgParty
|
2
4
|
module Adapter
|
3
5
|
module AbstractMethods
|
4
6
|
def create_range_partition(*)
|
5
|
-
raise
|
7
|
+
raise "#create_range_partition is not implemented"
|
6
8
|
end
|
7
9
|
|
8
10
|
def create_list_partition(*)
|
9
|
-
raise
|
11
|
+
raise "#create_list_partition is not implemented"
|
10
12
|
end
|
11
13
|
|
12
14
|
def create_range_partition_of(*)
|
13
|
-
raise
|
15
|
+
raise "#create_range_partition_of is not implemented"
|
14
16
|
end
|
15
17
|
|
16
18
|
def create_list_partition_of(*)
|
17
|
-
raise
|
19
|
+
raise "#create_list_partition_of is not implemented"
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_table_like(*)
|
23
|
+
raise "#create_table_like is not implemented"
|
18
24
|
end
|
19
25
|
|
20
26
|
def attach_range_partition(*)
|
21
|
-
raise
|
27
|
+
raise "#attach_range_partition is not implemented"
|
22
28
|
end
|
23
29
|
|
24
30
|
def attach_list_partition(*)
|
25
|
-
raise
|
31
|
+
raise "#attach_list_partition is not implemented"
|
26
32
|
end
|
27
33
|
|
28
34
|
def detach_partition(*)
|
29
|
-
raise
|
35
|
+
raise "#detach_partition is not implemented"
|
30
36
|
end
|
31
37
|
end
|
32
38
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pg_party/adapter_decorator"
|
2
4
|
|
3
5
|
module PgParty
|
@@ -19,6 +21,10 @@ module PgParty
|
|
19
21
|
PgParty::AdapterDecorator.new(self).create_list_partition_of(*args)
|
20
22
|
end
|
21
23
|
|
24
|
+
def create_table_like(*args)
|
25
|
+
PgParty::AdapterDecorator.new(self).create_table_like(*args)
|
26
|
+
end
|
27
|
+
|
22
28
|
def attach_range_partition(*args)
|
23
29
|
PgParty::AdapterDecorator.new(self).attach_range_partition(*args)
|
24
30
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "digest"
|
2
4
|
require "pg_party/cache"
|
3
5
|
|
@@ -18,47 +20,39 @@ module PgParty
|
|
18
20
|
end
|
19
21
|
|
20
22
|
def create_range_partition_of(table_name, start_range:, end_range:, **options)
|
21
|
-
|
22
|
-
child_table_name = options[:name]
|
23
|
-
else
|
24
|
-
child_table_name = hashed_table_name(table_name, "#{start_range}#{end_range}")
|
25
|
-
end
|
26
|
-
|
27
|
-
constraint_clause = "FROM (#{quote(start_range)}) TO (#{quote(end_range)})"
|
28
|
-
|
29
|
-
create_partition_of(table_name, child_table_name, constraint_clause, **options)
|
23
|
+
create_partition_of(table_name, range_constraint_clause(start_range, end_range), **options)
|
30
24
|
end
|
31
25
|
|
32
26
|
def create_list_partition_of(table_name, values:, **options)
|
33
|
-
|
34
|
-
|
35
|
-
else
|
36
|
-
child_table_name = hashed_table_name(table_name, values.to_s)
|
37
|
-
end
|
27
|
+
create_partition_of(table_name, list_constraint_clause(values), **options)
|
28
|
+
end
|
38
29
|
|
39
|
-
|
30
|
+
def create_table_like(table_name, new_table_name, **options)
|
31
|
+
primary_key = options.fetch(:primary_key) { calculate_primary_key(table_name) }
|
40
32
|
|
41
|
-
|
42
|
-
end
|
33
|
+
validate_primary_key(primary_key)
|
43
34
|
|
44
|
-
def attach_range_partition(parent_table_name, child_table_name, start_range:, end_range:)
|
45
35
|
execute(<<-SQL)
|
46
|
-
|
47
|
-
|
48
|
-
|
36
|
+
CREATE TABLE #{quote_table_name(new_table_name)} (
|
37
|
+
LIKE #{quote_table_name(table_name)} INCLUDING ALL
|
38
|
+
)
|
49
39
|
SQL
|
50
40
|
|
51
|
-
|
52
|
-
|
41
|
+
return if !primary_key
|
42
|
+
return if has_primary_key?(new_table_name)
|
53
43
|
|
54
|
-
def attach_list_partition(parent_table_name, child_table_name, values:)
|
55
44
|
execute(<<-SQL)
|
56
|
-
ALTER TABLE #{quote_table_name(
|
57
|
-
|
58
|
-
FOR VALUES IN (#{Array.wrap(values).map(&method(:quote)).join(",")})
|
45
|
+
ALTER TABLE #{quote_table_name(new_table_name)}
|
46
|
+
ADD PRIMARY KEY (#{quote_column_name(primary_key)})
|
59
47
|
SQL
|
48
|
+
end
|
60
49
|
|
61
|
-
|
50
|
+
def attach_range_partition(parent_table_name, child_table_name, start_range:, end_range:)
|
51
|
+
attach_partition(parent_table_name, child_table_name, range_constraint_clause(start_range, end_range))
|
52
|
+
end
|
53
|
+
|
54
|
+
def attach_list_partition(parent_table_name, child_table_name, values:)
|
55
|
+
attach_partition(parent_table_name, child_table_name, list_constraint_clause(values))
|
62
56
|
end
|
63
57
|
|
64
58
|
def detach_partition(parent_table_name, child_table_name)
|
@@ -73,16 +67,17 @@ module PgParty
|
|
73
67
|
private
|
74
68
|
|
75
69
|
def create_partition(table_name, type, partition_key, **options)
|
76
|
-
modified_options = options.except(:id, :primary_key)
|
70
|
+
modified_options = options.except(:id, :primary_key, :template)
|
71
|
+
template = options.fetch(:template, true)
|
77
72
|
id = options.fetch(:id, :bigserial)
|
78
|
-
primary_key = options.fetch(:primary_key
|
73
|
+
primary_key = options.fetch(:primary_key) { calculate_primary_key(table_name) }
|
79
74
|
|
80
|
-
|
75
|
+
validate_primary_key(primary_key)
|
81
76
|
|
82
77
|
modified_options[:id] = false
|
83
|
-
modified_options[:options] = "PARTITION BY #{type.to_s.upcase} (
|
78
|
+
modified_options[:options] = "PARTITION BY #{type.to_s.upcase} (#{quote_partition_key(partition_key)})"
|
84
79
|
|
85
|
-
|
80
|
+
create_table(table_name, modified_options) do |td|
|
86
81
|
if id == :uuid
|
87
82
|
td.column(primary_key, id, null: false, default: uuid_function)
|
88
83
|
elsif id
|
@@ -95,42 +90,35 @@ module PgParty
|
|
95
90
|
# Rails 4 has a bug where uuid columns are always nullable
|
96
91
|
change_column_null(table_name, primary_key, false) if id == :uuid
|
97
92
|
|
98
|
-
|
93
|
+
return unless template
|
94
|
+
|
95
|
+
create_table_like(table_name, template_table_name(table_name), primary_key: id && primary_key)
|
99
96
|
end
|
100
97
|
|
101
|
-
def create_partition_of(table_name,
|
102
|
-
|
103
|
-
|
104
|
-
|
98
|
+
def create_partition_of(table_name, constraint_clause, **options)
|
99
|
+
child_table_name = options.fetch(:name) { hashed_table_name(table_name, constraint_clause) }
|
100
|
+
primary_key = options.fetch(:primary_key) { calculate_primary_key(table_name) }
|
101
|
+
template_table_name = template_table_name(table_name)
|
105
102
|
|
106
|
-
|
103
|
+
if schema_cache.data_source_exists?(template_table_name)
|
104
|
+
create_table_like(template_table_name, child_table_name, primary_key: false)
|
105
|
+
else
|
106
|
+
create_table_like(table_name, child_table_name, primary_key: primary_key)
|
107
|
+
end
|
107
108
|
|
109
|
+
attach_partition(table_name, child_table_name, constraint_clause)
|
110
|
+
|
111
|
+
child_table_name
|
112
|
+
end
|
113
|
+
|
114
|
+
def attach_partition(parent_table_name, child_table_name, constraint_clause)
|
108
115
|
execute(<<-SQL)
|
109
|
-
|
110
|
-
PARTITION
|
116
|
+
ALTER TABLE #{quote_table_name(parent_table_name)}
|
117
|
+
ATTACH PARTITION #{quote_table_name(child_table_name)}
|
111
118
|
FOR VALUES #{constraint_clause}
|
112
119
|
SQL
|
113
120
|
|
114
|
-
if primary_key
|
115
|
-
execute(<<-SQL)
|
116
|
-
ALTER TABLE #{quote_table_name(child_table_name)}
|
117
|
-
ADD PRIMARY KEY (#{quote_column_name(primary_key)})
|
118
|
-
SQL
|
119
|
-
end
|
120
|
-
|
121
|
-
if index && partition_key && primary_key != partition_key
|
122
|
-
index_name = index_name(child_table_name, partition_key)
|
123
|
-
|
124
|
-
execute(<<-SQL)
|
125
|
-
CREATE INDEX #{quote_table_name(index_name)}
|
126
|
-
ON #{quote_table_name(child_table_name)}
|
127
|
-
USING btree ((#{quote_partition_key(partition_key)}))
|
128
|
-
SQL
|
129
|
-
end
|
130
|
-
|
131
121
|
PgParty::Cache.clear!
|
132
|
-
|
133
|
-
child_table_name
|
134
122
|
end
|
135
123
|
|
136
124
|
# Rails 5.2 now returns boolean literals
|
@@ -145,8 +133,40 @@ module PgParty
|
|
145
133
|
end
|
146
134
|
end
|
147
135
|
|
136
|
+
def has_primary_key?(table_name)
|
137
|
+
primary_key(table_name).present?
|
138
|
+
end
|
139
|
+
|
140
|
+
def calculate_primary_key(table_name)
|
141
|
+
ActiveRecord::Base.get_primary_key(table_name.to_s.singularize).to_sym
|
142
|
+
end
|
143
|
+
|
144
|
+
def validate_primary_key(key)
|
145
|
+
raise ArgumentError, "composite primary key not supported" if key.is_a?(Array)
|
146
|
+
end
|
147
|
+
|
148
148
|
def quote_partition_key(key)
|
149
|
-
key.
|
149
|
+
if key.is_a?(Proc)
|
150
|
+
key.call.to_s # very difficult to determine how to sanitize a complex expression
|
151
|
+
else
|
152
|
+
quote_column_name(key)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def quote_collection(values)
|
157
|
+
Array.wrap(values).map(&method(:quote)).join(",")
|
158
|
+
end
|
159
|
+
|
160
|
+
def template_table_name(table_name)
|
161
|
+
"#{table_name}_template"
|
162
|
+
end
|
163
|
+
|
164
|
+
def range_constraint_clause(start_range, end_range)
|
165
|
+
"FROM (#{quote_collection(start_range)}) TO (#{quote_collection(end_range)})"
|
166
|
+
end
|
167
|
+
|
168
|
+
def list_constraint_clause(values)
|
169
|
+
"IN (#{quote_collection(values.try(:to_a) || values)})"
|
150
170
|
end
|
151
171
|
|
152
172
|
def uuid_function
|
@@ -160,9 +180,5 @@ module PgParty
|
|
160
180
|
def supports_partitions?
|
161
181
|
__getobj__.send(:postgresql_version) >= 100000
|
162
182
|
end
|
163
|
-
|
164
|
-
def index_name(table_name, key)
|
165
|
-
"index_#{table_name}_on_#{key.to_s.split("::").join("_")}"
|
166
|
-
end
|
167
183
|
end
|
168
184
|
end
|
data/lib/pg_party/cache.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgParty
|
4
|
+
module Hacks
|
5
|
+
module SchemaCache
|
6
|
+
def self.included(base)
|
7
|
+
return if base.method_defined?(:data_source_exists?)
|
8
|
+
|
9
|
+
base.send(:alias_method, :data_source_exists?, :table_exists?)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pg_party/model_injector"
|
2
4
|
|
3
5
|
module PgParty
|
4
6
|
module Model
|
5
7
|
module Methods
|
6
|
-
def range_partition_by(key)
|
7
|
-
PgParty::ModelInjector.new(self, key).inject_range_methods
|
8
|
+
def range_partition_by(key=nil, &blk)
|
9
|
+
PgParty::ModelInjector.new(self, key || blk).inject_range_methods
|
8
10
|
end
|
9
11
|
|
10
|
-
def list_partition_by(key)
|
11
|
-
PgParty::ModelInjector.new(self, key).inject_list_methods
|
12
|
+
def list_partition_by(key=nil, &blk)
|
13
|
+
PgParty::ModelInjector.new(self, key || blk).inject_list_methods
|
12
14
|
end
|
13
15
|
|
14
16
|
def partitioned?
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pg_party/cache"
|
2
4
|
|
3
5
|
module PgParty
|
@@ -15,7 +17,7 @@ module PgParty
|
|
15
17
|
def partition_table_exists?
|
16
18
|
target_table = partitions.first || table_name
|
17
19
|
|
18
|
-
connection.schema_cache.
|
20
|
+
connection.schema_cache.data_source_exists?(target_table)
|
19
21
|
end
|
20
22
|
|
21
23
|
def in_partition(child_table_name)
|
@@ -44,17 +46,33 @@ module PgParty
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def partition_key_eq(value)
|
47
|
-
|
49
|
+
if complex_partition_key
|
50
|
+
complex_partition_key_query("(#{partition_key}) = (?)", value)
|
51
|
+
else
|
52
|
+
where(current_arel_table[partition_key].eq(value))
|
53
|
+
end
|
48
54
|
end
|
49
55
|
|
50
56
|
def range_partition_key_in(start_range, end_range)
|
51
|
-
|
57
|
+
if complex_partition_key
|
58
|
+
complex_partition_key_query(
|
59
|
+
"(#{partition_key}) >= (?) AND (#{partition_key}) < (?)",
|
60
|
+
start_range,
|
61
|
+
end_range
|
62
|
+
)
|
63
|
+
else
|
64
|
+
node = current_arel_table[partition_key]
|
52
65
|
|
53
|
-
|
66
|
+
where(node.gteq(start_range).and(node.lt(end_range)))
|
67
|
+
end
|
54
68
|
end
|
55
69
|
|
56
70
|
def list_partition_key_in(*values)
|
57
|
-
|
71
|
+
if complex_partition_key
|
72
|
+
complex_partition_key_query("(#{partition_key}) IN (?)", values.flatten)
|
73
|
+
else
|
74
|
+
where(current_arel_table[partition_key].in(values.flatten))
|
75
|
+
end
|
58
76
|
end
|
59
77
|
|
60
78
|
def partitions
|
@@ -67,6 +85,8 @@ module PgParty
|
|
67
85
|
WHERE pg_tables.tablename = #{connection.quote(table_name)}
|
68
86
|
SQL
|
69
87
|
end
|
88
|
+
rescue
|
89
|
+
[]
|
70
90
|
end
|
71
91
|
|
72
92
|
def create_range_partition(start_range:, end_range:, **options)
|
@@ -74,44 +94,57 @@ module PgParty
|
|
74
94
|
start_range: start_range,
|
75
95
|
end_range: end_range,
|
76
96
|
primary_key: primary_key,
|
77
|
-
partition_key: partition_key
|
78
97
|
)
|
79
98
|
|
80
|
-
|
99
|
+
create_partition(:create_range_partition_of, table_name, **modified_options)
|
81
100
|
end
|
82
101
|
|
83
102
|
def create_list_partition(values:, **options)
|
84
103
|
modified_options = options.merge(
|
85
104
|
values: values,
|
86
105
|
primary_key: primary_key,
|
87
|
-
partition_key: partition_key
|
88
106
|
)
|
89
107
|
|
90
|
-
|
108
|
+
create_partition(:create_list_partition_of, table_name, **modified_options)
|
91
109
|
end
|
92
110
|
|
93
111
|
private
|
94
112
|
|
113
|
+
def create_partition(migration_method, table_name, **options)
|
114
|
+
transaction { connection.send(migration_method, table_name, **options) }
|
115
|
+
end
|
116
|
+
|
95
117
|
def cache_key
|
96
118
|
__getobj__.object_id
|
97
119
|
end
|
98
120
|
|
99
|
-
|
100
|
-
|
101
|
-
|
121
|
+
# https://stackoverflow.com/questions/28685149/activerecord-query-with-aliasd-table-names
|
122
|
+
def current_arel_table
|
123
|
+
none.arel.source.left.tap do |node|
|
124
|
+
if [Arel::Table, Arel::Nodes::TableAlias].exclude?(node.class)
|
125
|
+
raise "could not find arel table in current scope"
|
126
|
+
end
|
102
127
|
end
|
103
128
|
end
|
104
129
|
|
105
|
-
def
|
106
|
-
|
130
|
+
def current_alias
|
131
|
+
arel_node = current_arel_table
|
107
132
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
arel_column
|
133
|
+
case arel_node
|
134
|
+
when Arel::Table
|
135
|
+
arel_node.name
|
136
|
+
when Arel::Nodes::TableAlias
|
137
|
+
arel_node.right
|
114
138
|
end
|
115
139
|
end
|
140
|
+
|
141
|
+
def complex_partition_key_query(clause, *interpolated_values)
|
142
|
+
subquery = base_class
|
143
|
+
.unscoped
|
144
|
+
.select("*")
|
145
|
+
.where(clause, *interpolated_values)
|
146
|
+
|
147
|
+
from(subquery, current_alias)
|
148
|
+
end
|
116
149
|
end
|
117
150
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PgParty
|
2
4
|
class ModelInjector
|
3
5
|
def initialize(model, key)
|
4
6
|
@model = model
|
5
7
|
@key = key
|
6
|
-
|
7
|
-
@column, @cast = key.to_s.split("::")
|
8
8
|
end
|
9
9
|
|
10
10
|
def inject_range_methods
|
@@ -33,15 +33,18 @@ module PgParty
|
|
33
33
|
def create_class_attributes
|
34
34
|
@model.class_attribute(
|
35
35
|
:partition_key,
|
36
|
-
:
|
37
|
-
:partition_cast,
|
36
|
+
:complex_partition_key,
|
38
37
|
instance_accessor: false,
|
39
38
|
instance_predicate: false
|
40
39
|
)
|
41
40
|
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
if @key.is_a?(Proc)
|
42
|
+
@model.partition_key = @key.call
|
43
|
+
@model.complex_partition_key = true
|
44
|
+
else
|
45
|
+
@model.partition_key = @key
|
46
|
+
@model.complex_partition_key = false
|
47
|
+
end
|
45
48
|
end
|
46
49
|
end
|
47
50
|
end
|
data/lib/pg_party/version.rb
CHANGED
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: 1.0.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:
|
11
|
+
date: 2019-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '4.2'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '6'
|
22
|
+
version: '6.1'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,21 +29,21 @@ dependencies:
|
|
29
29
|
version: '4.2'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '6'
|
32
|
+
version: '6.1'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: appraisal
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
39
|
+
version: '2.2'
|
40
40
|
type: :development
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
46
|
+
version: '2.2'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bundler
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,131 +59,131 @@ dependencies:
|
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '1.15'
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
62
|
+
name: byebug
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '
|
67
|
+
version: '10.0'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
74
|
+
version: '10.0'
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
|
-
name:
|
76
|
+
name: combustion
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
78
78
|
requirements:
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
81
|
+
version: '1.1'
|
82
82
|
type: :development
|
83
83
|
prerelease: false
|
84
84
|
version_requirements: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
88
|
+
version: '1.1'
|
89
89
|
- !ruby/object:Gem::Dependency
|
90
|
-
name:
|
90
|
+
name: database_cleaner
|
91
91
|
requirement: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: '1.
|
95
|
+
version: '1.6'
|
96
96
|
type: :development
|
97
97
|
prerelease: false
|
98
98
|
version_requirements: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
100
|
- - "~>"
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version: '1.
|
102
|
+
version: '1.6'
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
|
-
name:
|
104
|
+
name: nokogiri
|
105
105
|
requirement: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
107
|
- - "~>"
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
109
|
+
version: 1.9.1
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
112
|
version_requirements: !ruby/object:Gem::Requirement
|
113
113
|
requirements:
|
114
114
|
- - "~>"
|
115
115
|
- !ruby/object:Gem::Version
|
116
|
-
version:
|
116
|
+
version: 1.9.1
|
117
117
|
- !ruby/object:Gem::Dependency
|
118
|
-
name:
|
118
|
+
name: pry-byebug
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
120
120
|
requirements:
|
121
121
|
- - "~>"
|
122
122
|
- !ruby/object:Gem::Version
|
123
|
-
version: '
|
123
|
+
version: '3.4'
|
124
124
|
type: :development
|
125
125
|
prerelease: false
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
127
127
|
requirements:
|
128
128
|
- - "~>"
|
129
129
|
- !ruby/object:Gem::Version
|
130
|
-
version: '
|
130
|
+
version: '3.4'
|
131
131
|
- !ruby/object:Gem::Dependency
|
132
|
-
name:
|
132
|
+
name: rake
|
133
133
|
requirement: !ruby/object:Gem::Requirement
|
134
134
|
requirements:
|
135
135
|
- - "~>"
|
136
136
|
- !ruby/object:Gem::Version
|
137
|
-
version: '
|
137
|
+
version: '12.0'
|
138
138
|
type: :development
|
139
139
|
prerelease: false
|
140
140
|
version_requirements: !ruby/object:Gem::Requirement
|
141
141
|
requirements:
|
142
142
|
- - "~>"
|
143
143
|
- !ruby/object:Gem::Version
|
144
|
-
version: '
|
144
|
+
version: '12.0'
|
145
145
|
- !ruby/object:Gem::Dependency
|
146
|
-
name:
|
146
|
+
name: rspec-its
|
147
147
|
requirement: !ruby/object:Gem::Requirement
|
148
148
|
requirements:
|
149
149
|
- - "~>"
|
150
150
|
- !ruby/object:Gem::Version
|
151
|
-
version: '
|
151
|
+
version: '1.2'
|
152
152
|
type: :development
|
153
153
|
prerelease: false
|
154
154
|
version_requirements: !ruby/object:Gem::Requirement
|
155
155
|
requirements:
|
156
156
|
- - "~>"
|
157
157
|
- !ruby/object:Gem::Version
|
158
|
-
version: '
|
158
|
+
version: '1.2'
|
159
159
|
- !ruby/object:Gem::Dependency
|
160
|
-
name:
|
160
|
+
name: rspec-rails
|
161
161
|
requirement: !ruby/object:Gem::Requirement
|
162
162
|
requirements:
|
163
163
|
- - "~>"
|
164
164
|
- !ruby/object:Gem::Version
|
165
|
-
version: '
|
165
|
+
version: '3.6'
|
166
166
|
type: :development
|
167
167
|
prerelease: false
|
168
168
|
version_requirements: !ruby/object:Gem::Requirement
|
169
169
|
requirements:
|
170
170
|
- - "~>"
|
171
171
|
- !ruby/object:Gem::Version
|
172
|
-
version: '
|
172
|
+
version: '3.6'
|
173
173
|
- !ruby/object:Gem::Dependency
|
174
|
-
name:
|
174
|
+
name: rspec_junit_formatter
|
175
175
|
requirement: !ruby/object:Gem::Requirement
|
176
176
|
requirements:
|
177
177
|
- - "~>"
|
178
178
|
- !ruby/object:Gem::Version
|
179
|
-
version: '0.
|
179
|
+
version: '0.3'
|
180
180
|
type: :development
|
181
181
|
prerelease: false
|
182
182
|
version_requirements: !ruby/object:Gem::Requirement
|
183
183
|
requirements:
|
184
184
|
- - "~>"
|
185
185
|
- !ruby/object:Gem::Version
|
186
|
-
version: '0.
|
186
|
+
version: '0.3'
|
187
187
|
- !ruby/object:Gem::Dependency
|
188
188
|
name: simplecov
|
189
189
|
requirement: !ruby/object:Gem::Requirement
|
@@ -198,6 +198,20 @@ dependencies:
|
|
198
198
|
- - "~>"
|
199
199
|
- !ruby/object:Gem::Version
|
200
200
|
version: '0.15'
|
201
|
+
- !ruby/object:Gem::Dependency
|
202
|
+
name: timecop
|
203
|
+
requirement: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - "~>"
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: '0.9'
|
208
|
+
type: :development
|
209
|
+
prerelease: false
|
210
|
+
version_requirements: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - "~>"
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '0.9'
|
201
215
|
description: Migrations and model helpers for creating and managing PostgreSQL 10
|
202
216
|
partitions
|
203
217
|
email:
|
@@ -213,6 +227,7 @@ files:
|
|
213
227
|
- lib/pg_party/adapter/postgresql_methods.rb
|
214
228
|
- lib/pg_party/adapter_decorator.rb
|
215
229
|
- lib/pg_party/cache.rb
|
230
|
+
- lib/pg_party/hacks/schema_cache.rb
|
216
231
|
- lib/pg_party/model/list_methods.rb
|
217
232
|
- lib/pg_party/model/methods.rb
|
218
233
|
- lib/pg_party/model/range_methods.rb
|
@@ -239,8 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
239
254
|
- !ruby/object:Gem::Version
|
240
255
|
version: 1.8.11
|
241
256
|
requirements: []
|
242
|
-
|
243
|
-
rubygems_version: 2.6.11
|
257
|
+
rubygems_version: 3.0.3
|
244
258
|
signing_key:
|
245
259
|
specification_version: 4
|
246
260
|
summary: ActiveRecord PostgreSQL Partitioning
|