pgtk 0.31.5 → 0.31.7

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: 9986e9d0fb260d182d823661b28c2782b7cbc066348431c59f18a2541e53018f
4
- data.tar.gz: 50c04ad428b737284568c3cc134c7b5f0cff4d8cdf9b1a02430213a31e819701
3
+ metadata.gz: 9620e5581d667750f82a36568c6b64fcb27c2c588b93fc433869f1a430159128
4
+ data.tar.gz: 281908df6408d31c7ae69ff9ea4a4122881b7ae54c7906e87d10abf885d625d4
5
5
  SHA512:
6
- metadata.gz: 0f6bf39b221fcedfc9ffd9843ef1d6ca43834d695c9a980139f60dd4915c9582f3174c5047710ecad954f62e9fad42b0fac540b9efcf5946885b7cde7da6b747
7
- data.tar.gz: 64bd40eaa16ad9ee3684adc204d442b3ec8a19fe699c0cfc8323b19c098fcbf61ed3bf225d961bbb02cf1f16ee69b976035aaa5dd3661873ab4c3fbf00defd57
6
+ metadata.gz: f633a5925cbf6f8ad61247c898a7d58a59d8a64dcf7e23597f625ddfad816e856ae00b6b4960839abec9d763b931a4c519bce49c3bd2724fef38854e7c316c7e
7
+ data.tar.gz: a70e63d0772e40632333d382d4c8598a6a1ab60d72b3ce7bc8675ed12aced75acdc6117fbfccfc6fd9094e927136f7b9f983ba99491b673a392fd7fdb645597a
data/Gemfile.lock CHANGED
@@ -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/lib/pgtk/pool.rb CHANGED
@@ -209,6 +209,7 @@ class Pgtk::Pool
209
209
  @timeout = timeout
210
210
  @items = []
211
211
  @taken = []
212
+ @free = []
212
213
  @mutex = Mutex.new
213
214
  @condition = ConditionVariable.new
214
215
  end
@@ -218,6 +219,7 @@ class Pgtk::Pool
218
219
  if @items.size < @size
219
220
  @items << item
220
221
  @taken << false
222
+ @free << (@items.size - 1)
221
223
  else
222
224
  index = @items.index(item)
223
225
  if index.nil?
@@ -226,6 +228,7 @@ class Pgtk::Pool
226
228
  @items[index] = item
227
229
  end
228
230
  @taken[index] = false
231
+ @free << index
229
232
  end
230
233
  @condition.signal
231
234
  end
@@ -234,12 +237,12 @@ class Pgtk::Pool
234
237
  def pop
235
238
  @mutex.synchronize do
236
239
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
237
- while @taken.all? || @items.empty?
240
+ while @free.empty?
238
241
  remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
239
242
  raise(Busy, "No free connection appeared in the pool after #{@timeout}s of waiting") if remaining <= 0
240
243
  @condition.wait(@mutex, remaining)
241
244
  end
242
- index = @taken.index(false)
245
+ index = @free.shift
243
246
  @taken[index] = true
244
247
  @items[index]
245
248
  end
@@ -274,6 +277,7 @@ class Pgtk::Pool
274
277
  start = Time.now
275
278
  sql = query.is_a?(Array) ? query.join(' ') : query
276
279
  @conn.instance_variable_set(:@pgtk_last_query, sql)
280
+ @conn.instance_variable_set(:@pgtk_started_at, start)
277
281
  begin
278
282
  out =
279
283
  if args.empty?
@@ -351,6 +355,7 @@ class Pgtk::Pool
351
355
  transactions = { PG::Constants::PQTRANS_IDLE => 'IDLE', PG::Constants::PQTRANS_ACTIVE => 'ACTIVE',
352
356
  PG::Constants::PQTRANS_INTRANS => 'INTRANS', PG::Constants::PQTRANS_INERROR => 'INERROR',
353
357
  PG::Constants::PQTRANS_UNKNOWN => 'UNKNOWN' }
358
+ conn.instance_variable_set(:@pgtk_pid, conn.backend_pid)
354
359
  parts = [
355
360
  ' ',
356
361
  "##{conn.backend_pid}",
@@ -358,27 +363,35 @@ class Pgtk::Pool
358
363
  statuses.fetch(conn.status, "status=#{conn.status}"),
359
364
  transactions.fetch(conn.transaction_status, "transaction_status=#{conn.transaction_status}")
360
365
  ]
366
+ if conn.transaction_status != PG::Constants::PQTRANS_IDLE
367
+ started = conn.instance_variable_get(:@pgtk_started_at)
368
+ parts << started.ago if started
369
+ end
361
370
  if conn.transaction_status == PG::Constants::PQTRANS_ACTIVE
362
371
  running = conn.instance_variable_get(:@pgtk_last_query)
363
372
  parts << "running: #{running.gsub(/\s+/, ' ').strip.ellipsized(60)}" if running
364
373
  end
365
374
  parts.join(' ')
366
375
  rescue PG::ConnectionBad => e
367
- parts = [e.message.strip]
376
+ pid = conn.instance_variable_get(:@pgtk_pid)
377
+ parts = [' ']
378
+ parts << (pid ? "##{pid}" : '#?')
379
+ parts << e.message.gsub(/\s+/, ' ').strip
368
380
  closed = conn.instance_variable_get(:@pgtk_closed_at)
369
381
  parts << "#{closed.ago} ago" if closed
370
382
  reason = conn.instance_variable_get(:@pgtk_closed_reason)
371
- parts << "because: #{reason}" if reason
383
+ parts << "because: #{reason.gsub(/\s+/, ' ').strip}" if reason
372
384
  last = conn.instance_variable_get(:@pgtk_last_query)
373
385
  parts << "last query: #{last.gsub(/\s+/, ' ').strip.ellipsized(60)}" if last
374
- parts.join(', ')
386
+ "#{parts.shift} #{parts.shift} #{parts.join(', ')}"
375
387
  end
376
388
 
377
389
  def renew(conn, reason)
378
390
  begin
379
391
  unless conn.finished?
392
+ conn.instance_variable_set(:@pgtk_pid, conn.backend_pid)
380
393
  conn.instance_variable_set(:@pgtk_closed_at, Time.now)
381
- conn.instance_variable_set(:@pgtk_closed_reason, reason)
394
+ conn.instance_variable_set(:@pgtk_closed_reason, reason.gsub(/\s+/, ' ').strip)
382
395
  conn.close
383
396
  end
384
397
  rescue StandardError => e
data/lib/pgtk/retry.rb CHANGED
@@ -85,11 +85,14 @@ class Pgtk::Retry
85
85
  ].join("\n")
86
86
  end
87
87
 
88
- # Execute a SQL query with automatic retry for SELECT queries only.
89
- # Non-SELECT queries fail on the first error, since a failure may occur
90
- # after the server received the query but before the acknowledgement
91
- # reached the client, and retrying a non-idempotent write could duplicate
92
- # it.
88
+ # 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.
93
96
  #
94
97
  # @param [String] sql The SQL query with params inside (possibly)
95
98
  # @return [Array] Result rows
@@ -98,6 +101,10 @@ class Pgtk::Retry
98
101
  attempt = 0
99
102
  begin
100
103
  @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
101
108
  rescue StandardError, Pgtk::Impatient::TooSlow => e
102
109
  raise(e) unless query.strip.upcase.start_with?('SELECT')
103
110
  attempt += 1
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.5' unless defined?(VERSION)
13
+ VERSION = '0.31.7' unless defined?(VERSION)
14
14
  end
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.5
4
+ version: 0.31.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko