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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 117996e543551102eebf83be228996818dbfdbfc324f7f45b5d346a57d39fc1a
4
- data.tar.gz: 22bd0738c88e60ed29ad3310a87755709dc4512d6b92932c88442253c29847f3
3
+ metadata.gz: 10ac49e5f5fa5ac3792edf7e62961f8d37f8937a645a878de4e15b3ced01e575
4
+ data.tar.gz: 51b1c1927f57ea50b181cda95dc6f0471237c4873a7e99482b35f980fd8294dd
5
5
  SHA512:
6
- metadata.gz: 0ae1185f452400e2f2f984e5266bdb9c6b8dc7d92b798eb714d18903949f230d8707911b562431984f4fa52178dc52198bfeac5b24d2aaeff918ee0b36804541
7
- data.tar.gz: 4b8fc6853d556967173556f0067a6c611b4a93be0440c31659b0892f7d944606fbc19f15e27b1361b06cacea99e268bb81008893dfcc910e56103c10d0b9c42c
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.5)
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.2)
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 without delay
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
 
@@ -7,7 +7,7 @@ require 'nokogiri'
7
7
  require 'rake/tasklib'
8
8
  require_relative '../pgtk'
9
9
 
10
- # Liquicheck rake task for check Liquibase XML files.
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 not suffix in #{context} context") if context
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 of
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 on any error, since reads are idempotent.
90
- # Non-SELECT queries are retried only on PG::ConnectionBad, since by
91
- # definition the query never reached the server, so retrying cannot
92
- # duplicate a write. Other errors on writes propagate immediately,
93
- # because a failure may occur after the server received the query but
94
- # before the acknowledgement reached the client, and retrying a
95
- # non-idempotent write could duplicate it.
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
- tables.each do |t|
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
@@ -10,5 +10,5 @@ require_relative '../pgtk'
10
10
  # Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
11
11
  # License:: MIT
12
12
  module Pgtk
13
- VERSION = '0.31.6' unless defined?(VERSION)
13
+ VERSION = '0.31.8' unless defined?(VERSION)
14
14
  end
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.10</postgresql.version>
13
+ <postgresql.version>42.7.11</postgresql.version>
14
14
  <liquibase.version>5.0.2</liquibase.version>
15
15
  </properties>
16
16
  <dependencies>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgtk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.6
4
+ version: 0.31.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko