pgtk 0.31.6 → 0.31.8
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/Gemfile.lock +2 -2
- data/README.md +3 -1
- data/lib/pgtk/liquicheck_task.rb +2 -2
- data/lib/pgtk/pool.rb +2 -1
- data/lib/pgtk/retry.rb +12 -11
- data/lib/pgtk/stash.rb +32 -1
- data/lib/pgtk/version.rb +1 -1
- data/resources/pom.xml +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 10ac49e5f5fa5ac3792edf7e62961f8d37f8937a645a878de4e15b3ced01e575
|
|
4
|
+
data.tar.gz: 51b1c1927f57ea50b181cda95dc6f0471237c4873a7e99482b35f980fd8294dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d1f4a4d1247426d01caea364c9082e6dc9b81eb7428fb2da3f11764d9542ad7c99b35b1f0a1ed25c033e203d9ef253b48b061ce29eee07ca2048f9f82aa8f50
|
|
7
|
+
data.tar.gz: f713deaf5f0b8c2ba45a0d6fd8d6c46fb88f8ba153b1f1096aa69c351655cd7ce1daefb7d6e0e730609eb96b713c90ad5b3a50ccb08914a10ee4011b4ba3ef2b
|
data/Gemfile.lock
CHANGED
|
@@ -42,7 +42,7 @@ GEM
|
|
|
42
42
|
loog (0.8.0)
|
|
43
43
|
ellipsized
|
|
44
44
|
logger (~> 1.0)
|
|
45
|
-
minitest (6.0.
|
|
45
|
+
minitest (6.0.6)
|
|
46
46
|
drb (~> 2.0)
|
|
47
47
|
prism (~> 1.5)
|
|
48
48
|
minitest-mock (5.27.0)
|
|
@@ -81,7 +81,7 @@ GEM
|
|
|
81
81
|
pg (1.6.3-x86_64-linux)
|
|
82
82
|
pg (1.6.3-x86_64-linux-musl)
|
|
83
83
|
prism (1.9.0)
|
|
84
|
-
qbash (0.8.
|
|
84
|
+
qbash (0.8.3)
|
|
85
85
|
backtrace (> 0)
|
|
86
86
|
elapsed (> 0)
|
|
87
87
|
loog (> 0)
|
data/README.md
CHANGED
|
@@ -284,7 +284,9 @@ retry_pool.exec('INSERT INTO logs (message) VALUES ($1)', ['User logged in'])
|
|
|
284
284
|
Key features:
|
|
285
285
|
|
|
286
286
|
1. Only `SELECT` queries are retried (to prevent duplicate data modifications)
|
|
287
|
-
2. Retries happen immediately
|
|
287
|
+
2. Retries happen immediately, except on `PG::ConnectionBad`,
|
|
288
|
+
where an exponential backoff (50ms, 200ms, 1s) is applied
|
|
289
|
+
between attempts to avoid amplifying upstream login storms
|
|
288
290
|
3. The original error is raised after all retry attempts are exhausted
|
|
289
291
|
4. Works seamlessly with other decorators like `Pgtk::Spy` and `Pgtk::Impatient`
|
|
290
292
|
|
data/lib/pgtk/liquicheck_task.rb
CHANGED
|
@@ -7,7 +7,7 @@ require 'nokogiri'
|
|
|
7
7
|
require 'rake/tasklib'
|
|
8
8
|
require_relative '../pgtk'
|
|
9
9
|
|
|
10
|
-
# Liquicheck rake task
|
|
10
|
+
# Liquicheck rake task to check Liquibase XML files.
|
|
11
11
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
12
12
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
13
13
|
# License:: MIT
|
|
@@ -63,7 +63,7 @@ class Pgtk::LiquicheckTask < Rake::TaskLib
|
|
|
63
63
|
context = node.attr('context')&.to_s
|
|
64
64
|
on(errors, file) do
|
|
65
65
|
demand(id, 'ID is empty')
|
|
66
|
-
confirm(id, /[-a-z]+/, "ID #{id.inspect} has
|
|
66
|
+
confirm(id, /[-a-z]+/, "ID #{id.inspect} has no suffix in #{context} context") if context
|
|
67
67
|
end
|
|
68
68
|
on(errors, file) do
|
|
69
69
|
demand(author, 'author is empty')
|
data/lib/pgtk/pool.rb
CHANGED
|
@@ -114,7 +114,7 @@ class Pgtk::Pool
|
|
|
114
114
|
# puts 'Title: ' + row['title']
|
|
115
115
|
# end
|
|
116
116
|
#
|
|
117
|
-
# All values in the retrieved hash are strings. No matter what types
|
|
117
|
+
# All values in the retrieved hash are strings. No matter what types
|
|
118
118
|
# of data you have in the database, you get strings here. It's your job
|
|
119
119
|
# to convert them to the type you need.
|
|
120
120
|
#
|
|
@@ -322,6 +322,7 @@ class Pgtk::Pool
|
|
|
322
322
|
conn = renew(conn, reason)
|
|
323
323
|
rescue StandardError => e
|
|
324
324
|
@log.warn("Failed to renew dead connection (#{reason}): #{e.message}")
|
|
325
|
+
raise(e)
|
|
325
326
|
end
|
|
326
327
|
end
|
|
327
328
|
begin
|
data/lib/pgtk/retry.rb
CHANGED
|
@@ -54,6 +54,8 @@ class Pgtk::Retry
|
|
|
54
54
|
# so its message and stack trace are preserved for debugging.
|
|
55
55
|
class Exhausted < StandardError; end
|
|
56
56
|
|
|
57
|
+
BACKOFFS = [0.05, 0.2, 1.0].freeze
|
|
58
|
+
|
|
57
59
|
# Constructor.
|
|
58
60
|
#
|
|
59
61
|
# @param [Pgtk::Pool] pool The pool to decorate
|
|
@@ -86,13 +88,15 @@ class Pgtk::Retry
|
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
# Execute a SQL query with automatic retry on transient failures.
|
|
89
|
-
# SELECT queries are retried
|
|
90
|
-
# Non-SELECT queries
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
91
|
+
# Only SELECT queries are retried, since reads are idempotent.
|
|
92
|
+
# Non-SELECT queries propagate the original error immediately, even
|
|
93
|
+
# on PG::ConnectionBad, because that error can be raised after the
|
|
94
|
+
# server already received the query but before the acknowledgement
|
|
95
|
+
# reached the client, and retrying a non-idempotent write could
|
|
96
|
+
# duplicate it. When the underlying error is PG::ConnectionBad, an
|
|
97
|
+
# exponential backoff (see BACKOFFS) is applied between attempts, so
|
|
98
|
+
# that a SELECT failing against an upstream pool that is in its
|
|
99
|
+
# login-failure cache window does not amplify the storm.
|
|
96
100
|
#
|
|
97
101
|
# @param [String] sql The SQL query with params inside (possibly)
|
|
98
102
|
# @return [Array] Result rows
|
|
@@ -101,14 +105,11 @@ class Pgtk::Retry
|
|
|
101
105
|
attempt = 0
|
|
102
106
|
begin
|
|
103
107
|
@pool.exec(sql, *)
|
|
104
|
-
rescue PG::ConnectionBad => e
|
|
105
|
-
attempt += 1
|
|
106
|
-
raise(Exhausted, "Retry gave up after #{@attempts} attempts: #{e.message}") if attempt >= @attempts
|
|
107
|
-
retry
|
|
108
108
|
rescue StandardError, Pgtk::Impatient::TooSlow => e
|
|
109
109
|
raise(e) unless query.strip.upcase.start_with?('SELECT')
|
|
110
110
|
attempt += 1
|
|
111
111
|
raise(Exhausted, "Retry gave up after #{@attempts} attempts: #{e.message}") if attempt >= @attempts
|
|
112
|
+
sleep(BACKOFFS[attempt - 1] || BACKOFFS.last) if e.is_a?(PG::ConnectionBad)
|
|
112
113
|
retry
|
|
113
114
|
end
|
|
114
115
|
end
|
data/lib/pgtk/stash.rb
CHANGED
|
@@ -89,6 +89,7 @@ class Pgtk::Stash
|
|
|
89
89
|
# @return [void]
|
|
90
90
|
def start!
|
|
91
91
|
@pool.start!
|
|
92
|
+
cascade!
|
|
92
93
|
launch!
|
|
93
94
|
end
|
|
94
95
|
|
|
@@ -218,8 +219,9 @@ class Pgtk::Stash
|
|
|
218
219
|
tables.uniq!
|
|
219
220
|
ret = @pool.exec(pure, params, result)
|
|
220
221
|
now = Time.now
|
|
222
|
+
affected = (tables + tables.flat_map { |t| @cascades&.fetch(t, []) || [] }).uniq
|
|
221
223
|
@entrance.with_write_lock do
|
|
222
|
-
|
|
224
|
+
affected.each do |t|
|
|
223
225
|
@stash[:table_mod][t] = now
|
|
224
226
|
@stash[:tables][t]&.each do |q|
|
|
225
227
|
@stash[:queries][q]&.each_key do |key|
|
|
@@ -280,6 +282,35 @@ class Pgtk::Stash
|
|
|
280
282
|
end
|
|
281
283
|
end
|
|
282
284
|
|
|
285
|
+
# Discover ON DELETE CASCADE / ON UPDATE CASCADE foreign keys so that a
|
|
286
|
+
# modify on the parent table also invalidates cached queries on children.
|
|
287
|
+
#
|
|
288
|
+
# @return [nil]
|
|
289
|
+
def cascade!
|
|
290
|
+
direct = Hash.new { |h, k| h[k] = [] }
|
|
291
|
+
@pool.exec(<<~SQL).each { |r| direct[r['parent']] << r['child'] }
|
|
292
|
+
SELECT tc.table_name AS child, ccu.table_name AS parent
|
|
293
|
+
FROM information_schema.table_constraints tc
|
|
294
|
+
JOIN information_schema.referential_constraints rc
|
|
295
|
+
ON tc.constraint_name = rc.constraint_name
|
|
296
|
+
AND tc.table_schema = rc.constraint_schema
|
|
297
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
298
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
299
|
+
AND ccu.table_schema = tc.table_schema
|
|
300
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
301
|
+
AND (rc.delete_rule = 'CASCADE' OR rc.update_rule = 'CASCADE')
|
|
302
|
+
AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
303
|
+
SQL
|
|
304
|
+
@cascades = direct.keys.to_h { |p| [p, transitive(p, direct, []).uniq] }
|
|
305
|
+
nil
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def transitive(parent, direct, seen)
|
|
309
|
+
return [] if seen.include?(parent)
|
|
310
|
+
seen << parent
|
|
311
|
+
direct[parent].flat_map { |c| [c] + transitive(c, direct, seen) }
|
|
312
|
+
end
|
|
313
|
+
|
|
283
314
|
# Launch background tasks for cache management.
|
|
284
315
|
#
|
|
285
316
|
# @return [nil]
|
data/lib/pgtk/version.rb
CHANGED
data/resources/pom.xml
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<version>0.0.0</version>
|
|
11
11
|
<packaging>pom</packaging>
|
|
12
12
|
<properties>
|
|
13
|
-
<postgresql.version>42.7.
|
|
13
|
+
<postgresql.version>42.7.11</postgresql.version>
|
|
14
14
|
<liquibase.version>5.0.2</liquibase.version>
|
|
15
15
|
</properties>
|
|
16
16
|
<dependencies>
|