pg_party 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +26 -0
- data/lib/pg_party.rb +26 -0
- data/lib/pg_party/adapter_decorator.rb +2 -3
- data/lib/pg_party/cache.rb +50 -16
- data/lib/pg_party/config.rb +16 -0
- data/lib/pg_party/hacks/database_tasks.rb +31 -0
- data/lib/pg_party/model/shared_methods.rb +10 -2
- data/lib/pg_party/model_decorator.rb +7 -20
- data/lib/pg_party/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5996516e7f3b76373b6fc4f9a29c9e31f48d9edb086b7e0f217031f0ed3707ea
|
4
|
+
data.tar.gz: d4e5d12b2bdb80ab928153efe9ec49e11b7e6e413f0a0edbeddfe2f24a7d4276
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fac73650e2065c300fabb4ad826e59b8877310033490778b81835d28f297135796befec74a3ca5f6f6d5b3204f65a0f6686d58875df53e1332d96b6ef09edbac
|
7
|
+
data.tar.gz: 0fec1b06196dc2fecd95464694ae1d9742fb72687ac676b5a6def977b1a965a2f137b70edb85310f273237d8a7f6370aaa6a193d5c926487e30710d53de720f0
|
data/README.md
CHANGED
@@ -48,6 +48,32 @@ $ gem install pg_party
|
|
48
48
|
Note that the gemspec does not require `pg`, as some model methods _may_ work for other databases.
|
49
49
|
Migration methods will be unavailable unless `pg` is installed.
|
50
50
|
|
51
|
+
## Configuration
|
52
|
+
|
53
|
+
These values can be accessed and set via `PgParty.config` and `PgParty.configure`.
|
54
|
+
|
55
|
+
- `caching`
|
56
|
+
- Whether to cache currently attached partitions and anonymous model classes
|
57
|
+
- Default: `true`
|
58
|
+
- `caching_ttl`
|
59
|
+
- Length of time (in seconds) that cache entries are considered valid
|
60
|
+
- Default: `-1` (never expire cache entries)
|
61
|
+
- `schema_exclude_partitions`
|
62
|
+
- Whether to exclude child partitions in `rake db:structure:dump` (supported in ActiveRecord 5.2+)
|
63
|
+
- Default: `true`
|
64
|
+
|
65
|
+
Note that caching is done in-memory for each process of an application. Attaching / detaching partitions _will_ clear the cache, but only for the process that initiated the request. For multi-process web servers, it is recommended to use a TTL or disable caching entirely.
|
66
|
+
|
67
|
+
### Example
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# in a Rails initializer
|
71
|
+
PgParty.configure do |c|
|
72
|
+
c.caching_ttl = 60
|
73
|
+
c.schema_exclude_partitions = false
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
51
77
|
## Usage
|
52
78
|
|
53
79
|
### Migrations
|
data/lib/pg_party.rb
CHANGED
@@ -1,8 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "pg_party/version"
|
4
|
+
require "pg_party/config"
|
5
|
+
require "pg_party/cache"
|
4
6
|
require "active_support"
|
5
7
|
|
8
|
+
module PgParty
|
9
|
+
@config = Config.new
|
10
|
+
@cache = Cache.new
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_reader :config, :cache
|
14
|
+
|
15
|
+
def configure(&blk)
|
16
|
+
blk.call(config)
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset
|
20
|
+
@config = Config.new
|
21
|
+
@cache = Cache.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
6
26
|
ActiveSupport.on_load(:active_record) do
|
7
27
|
require "pg_party/model/methods"
|
8
28
|
|
@@ -20,6 +40,12 @@ ActiveSupport.on_load(:active_record) do
|
|
20
40
|
PgParty::Hacks::SchemaCache
|
21
41
|
)
|
22
42
|
|
43
|
+
require "pg_party/hacks/database_tasks"
|
44
|
+
|
45
|
+
ActiveRecord::Tasks::DatabaseTasks.extend(
|
46
|
+
PgParty::Hacks::DatabaseTasks
|
47
|
+
)
|
48
|
+
|
23
49
|
begin
|
24
50
|
require "active_record/connection_adapters/postgresql_adapter"
|
25
51
|
require "pg_party/adapter/postgresql_methods"
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "digest"
|
4
|
-
require "pg_party/cache"
|
5
4
|
|
6
5
|
module PgParty
|
7
6
|
class AdapterDecorator < SimpleDelegator
|
@@ -61,7 +60,7 @@ module PgParty
|
|
61
60
|
DETACH PARTITION #{quote_table_name(child_table_name)}
|
62
61
|
SQL
|
63
62
|
|
64
|
-
PgParty
|
63
|
+
PgParty.cache.clear!
|
65
64
|
end
|
66
65
|
|
67
66
|
private
|
@@ -118,7 +117,7 @@ module PgParty
|
|
118
117
|
FOR VALUES #{constraint_clause}
|
119
118
|
SQL
|
120
119
|
|
121
|
-
PgParty
|
120
|
+
PgParty.cache.clear!
|
122
121
|
end
|
123
122
|
|
124
123
|
# Rails 5.2 now returns boolean literals
|
data/lib/pg_party/cache.rb
CHANGED
@@ -6,32 +6,66 @@ module PgParty
|
|
6
6
|
class Cache
|
7
7
|
LOCK = Mutex.new
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
def initialize
|
10
|
+
# automatically initialize a new hash when
|
11
|
+
# accessing an object id that doesn't exist
|
12
|
+
@store = Hash.new { |h, k| h[k] = { models: {}, partitions: nil } }
|
13
|
+
end
|
14
|
+
|
15
|
+
def clear!
|
16
|
+
LOCK.synchronize { @store.clear }
|
17
|
+
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_model(key, child_table, &block)
|
22
|
+
return block.call unless caching_enabled?
|
23
|
+
|
24
|
+
LOCK.synchronize { fetch_value(@store[key][:models], child_table.to_sym, block) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_partitions(key, &block)
|
28
|
+
return block.call unless caching_enabled?
|
29
|
+
|
30
|
+
LOCK.synchronize { fetch_value(@store[key], :partitions, block) }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def caching_enabled?
|
36
|
+
PgParty.config.caching
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch_value(subhash, key, block)
|
40
|
+
entry = subhash[key]
|
12
41
|
|
13
|
-
|
42
|
+
if entry.nil? || entry.expired?
|
43
|
+
entry = Entry.new(block.call)
|
44
|
+
subhash[key] = entry
|
14
45
|
end
|
15
46
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
47
|
+
entry.value
|
48
|
+
end
|
49
|
+
|
50
|
+
class Entry
|
51
|
+
attr_reader :value
|
52
|
+
|
53
|
+
def initialize(value)
|
54
|
+
@value = value
|
55
|
+
@timestamp = Time.now
|
20
56
|
end
|
21
57
|
|
22
|
-
def
|
23
|
-
|
24
|
-
store[key][:partitions] ||= block.call
|
25
|
-
end
|
58
|
+
def expired?
|
59
|
+
ttl.positive? && Time.now - @timestamp > ttl
|
26
60
|
end
|
27
61
|
|
28
62
|
private
|
29
63
|
|
30
|
-
def
|
31
|
-
|
32
|
-
# accessing an object id that doesn't exist
|
33
|
-
@store ||= Hash.new { |h, k| h[k] = { models: {}, partitions: nil } }
|
64
|
+
def ttl
|
65
|
+
PgParty.config.caching_ttl
|
34
66
|
end
|
35
67
|
end
|
68
|
+
|
69
|
+
private_constant :Entry
|
36
70
|
end
|
37
71
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgParty
|
4
|
+
class Config
|
5
|
+
attr_accessor \
|
6
|
+
:caching,
|
7
|
+
:caching_ttl,
|
8
|
+
:schema_exclude_partitions
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@caching = true
|
12
|
+
@caching_ttl = -1
|
13
|
+
@schema_exclude_partitions = true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgParty
|
4
|
+
module Hacks
|
5
|
+
module DatabaseTasks
|
6
|
+
def structure_dump(*)
|
7
|
+
old_ignore_list = ActiveRecord::SchemaDumper.ignore_tables
|
8
|
+
|
9
|
+
if PgParty.config.schema_exclude_partitions
|
10
|
+
new_ignore_list = partitions.map { |table| "*.#{table}" }
|
11
|
+
else
|
12
|
+
new_ignore_list = []
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveRecord::SchemaDumper.ignore_tables = old_ignore_list + new_ignore_list
|
16
|
+
|
17
|
+
super
|
18
|
+
ensure
|
19
|
+
ActiveRecord::SchemaDumper.ignore_tables = old_ignore_list
|
20
|
+
end
|
21
|
+
|
22
|
+
def partitions
|
23
|
+
ActiveRecord::Base.connection.select_values(
|
24
|
+
"SELECT DISTINCT inhrelid::regclass::text FROM pg_inherits"
|
25
|
+
)
|
26
|
+
rescue
|
27
|
+
[]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -6,11 +6,19 @@ module PgParty
|
|
6
6
|
module Model
|
7
7
|
module SharedMethods
|
8
8
|
def reset_primary_key
|
9
|
-
|
9
|
+
if self != base_class
|
10
|
+
base_class.primary_key
|
11
|
+
elsif partition_name = partitions.first
|
12
|
+
in_partition(partition_name).get_primary_key(base_class.name)
|
13
|
+
else
|
14
|
+
get_primary_key(base_class.name)
|
15
|
+
end
|
10
16
|
end
|
11
17
|
|
12
18
|
def table_exists?
|
13
|
-
|
19
|
+
target_table = partitions.first || table_name
|
20
|
+
|
21
|
+
connection.schema_cache.data_source_exists?(target_table)
|
14
22
|
end
|
15
23
|
|
16
24
|
def partitions
|
@@ -1,27 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "pg_party/cache"
|
4
|
-
|
5
3
|
module PgParty
|
6
4
|
class ModelDecorator < SimpleDelegator
|
7
|
-
def partition_primary_key
|
8
|
-
if self != base_class
|
9
|
-
base_class.primary_key
|
10
|
-
elsif partition_name = partitions.first
|
11
|
-
in_partition(partition_name).get_primary_key(base_class.name)
|
12
|
-
else
|
13
|
-
get_primary_key(base_class.name)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def partition_table_exists?
|
18
|
-
target_table = partitions.first || table_name
|
19
|
-
|
20
|
-
connection.schema_cache.data_source_exists?(target_table)
|
21
|
-
end
|
22
|
-
|
23
5
|
def in_partition(child_table_name)
|
24
|
-
PgParty
|
6
|
+
PgParty.cache.fetch_model(cache_key, child_table_name) do
|
25
7
|
Class.new(__getobj__) do
|
26
8
|
self.table_name = child_table_name
|
27
9
|
|
@@ -41,6 +23,11 @@ module PgParty
|
|
41
23
|
def self.new(*args, &blk)
|
42
24
|
superclass.new(*args, &blk)
|
43
25
|
end
|
26
|
+
|
27
|
+
# to avoid unnecessary db lookups
|
28
|
+
def self.partitions
|
29
|
+
[]
|
30
|
+
end
|
44
31
|
end
|
45
32
|
end
|
46
33
|
end
|
@@ -76,7 +63,7 @@ module PgParty
|
|
76
63
|
end
|
77
64
|
|
78
65
|
def partitions
|
79
|
-
PgParty
|
66
|
+
PgParty.cache.fetch_partitions(cache_key) do
|
80
67
|
connection.select_values(<<-SQL)
|
81
68
|
SELECT pg_inherits.inhrelid::regclass::text
|
82
69
|
FROM pg_tables
|
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: 1.
|
4
|
+
version: 1.2.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: 2019-
|
11
|
+
date: 2019-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -219,6 +219,8 @@ files:
|
|
219
219
|
- lib/pg_party/adapter/postgresql_methods.rb
|
220
220
|
- lib/pg_party/adapter_decorator.rb
|
221
221
|
- lib/pg_party/cache.rb
|
222
|
+
- lib/pg_party/config.rb
|
223
|
+
- lib/pg_party/hacks/database_tasks.rb
|
222
224
|
- lib/pg_party/hacks/schema_cache.rb
|
223
225
|
- lib/pg_party/model/list_methods.rb
|
224
226
|
- lib/pg_party/model/methods.rb
|