pgtk 0.32.5 → 0.32.6

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: b1997e1fa9261d1881e5df4495a43723ea737ad754806bc9908a986c4e782fa6
4
- data.tar.gz: 51b31ffb42b6eb9d2ea625c82be4664384bc190d2c1187ab642cb7cbeaaeb9ab
3
+ metadata.gz: bff4002fc7f88a973b99757aa0b8be3b82b6631b87f94c8ade93b30983b21761
4
+ data.tar.gz: 707d5ec1bf05047d60a0f25103b7210751c4731f79caa8b08c3021e7e3810e6c
5
5
  SHA512:
6
- metadata.gz: f6245814e1907f27948dd369e8ff00cf584e759c04c7b22befac1c4da3de828ef6192f8d3510fe6760b04cdbab871045a7963ed54c00cd1d64b982b2ccfb483d
7
- data.tar.gz: 5f97bf03bf00fc4e4da554b1807896474d4221748ebacc3cbda22da56e6d55e3067dd5e277082c7a4184aabe4617df9ece91e6dfaaec718f2530f7acc4410664
6
+ metadata.gz: 210230feda441fdee55cb3c3b6ccf1e2335efcdef77e95996afe89d3a4caf4f21bd8aa4fb9fd19f9b636d1336cc821345aa7454ec474d1693dff21990f2a1b40
7
+ data.tar.gz: ab98c8f9cf29405c495bd4808855e1107889ccdc2ee9011f4abe431b561f05d033564dc587f5a3084f64f0842e935d4bee6b9c8265cc729fe762a0f99bab4eb9
data/Gemfile.lock CHANGED
@@ -2,7 +2,7 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  pgtk (0.0.0)
5
- backtrace (~> 0.4)
5
+ cgi (~> 0.0)
6
6
  concurrent-ruby (~> 1.3)
7
7
  donce (~> 0.0)
8
8
  ellipsized (~> 0.3)
@@ -22,7 +22,8 @@ GEM
22
22
  ast (2.4.3)
23
23
  backtrace (0.4.1)
24
24
  builder (3.3.0)
25
- concurrent-ruby (1.3.6)
25
+ cgi (0.5.1)
26
+ concurrent-ruby (1.3.7)
26
27
  differ (0.1.2)
27
28
  docile (1.4.1)
28
29
  donce (0.7.0)
@@ -35,7 +36,7 @@ GEM
35
36
  tago (~> 0.1)
36
37
  ellipsized (0.3.0)
37
38
  joined (0.4.0)
38
- json (2.19.5)
39
+ json (2.19.9)
39
40
  language_server-protocol (3.17.0.5)
40
41
  lint_roller (1.1.0)
41
42
  logger (1.7.0)
@@ -52,21 +53,21 @@ GEM
52
53
  minitest (>= 5.0, < 7)
53
54
  ruby-progressbar
54
55
  minitest-stub-const (0.6)
55
- nokogiri (1.19.3-aarch64-linux-gnu)
56
+ nokogiri (1.19.4-aarch64-linux-gnu)
56
57
  racc (~> 1.4)
57
- nokogiri (1.19.3-aarch64-linux-musl)
58
+ nokogiri (1.19.4-aarch64-linux-musl)
58
59
  racc (~> 1.4)
59
- nokogiri (1.19.3-arm-linux-gnu)
60
+ nokogiri (1.19.4-arm-linux-gnu)
60
61
  racc (~> 1.4)
61
- nokogiri (1.19.3-arm-linux-musl)
62
+ nokogiri (1.19.4-arm-linux-musl)
62
63
  racc (~> 1.4)
63
- nokogiri (1.19.3-arm64-darwin)
64
+ nokogiri (1.19.4-arm64-darwin)
64
65
  racc (~> 1.4)
65
- nokogiri (1.19.3-x86_64-darwin)
66
+ nokogiri (1.19.4-x86_64-darwin)
66
67
  racc (~> 1.4)
67
- nokogiri (1.19.3-x86_64-linux-gnu)
68
+ nokogiri (1.19.4-x86_64-linux-gnu)
68
69
  racc (~> 1.4)
69
- nokogiri (1.19.3-x86_64-linux-musl)
70
+ nokogiri (1.19.4-x86_64-linux-musl)
70
71
  racc (~> 1.4)
71
72
  os (1.1.4)
72
73
  parallel (2.1.0)
@@ -94,7 +95,7 @@ GEM
94
95
  tago (~> 0.0)
95
96
  regexp_parser (2.12.0)
96
97
  rexml (3.4.4)
97
- rubocop (1.86.2)
98
+ rubocop (1.87.0)
98
99
  json (~> 2.3)
99
100
  language_server-protocol (~> 3.17.0.2)
100
101
  lint_roller (~> 1.1.0)
@@ -108,7 +109,7 @@ GEM
108
109
  rubocop-ast (1.49.1)
109
110
  parser (>= 3.3.7.2)
110
111
  prism (~> 1.7)
111
- rubocop-elegant (0.5.0)
112
+ rubocop-elegant (0.6.0)
112
113
  lint_roller (~> 1.1)
113
114
  rubocop (~> 1.75)
114
115
  rubocop-minitest (0.39.1)
@@ -147,7 +148,7 @@ GEM
147
148
  nokogiri (~> 1.8)
148
149
  rainbow (~> 3.0)
149
150
  slop (~> 4.4)
150
- yard (0.9.43)
151
+ yard (0.9.44)
151
152
 
152
153
  PLATFORMS
153
154
  aarch64-linux
@@ -63,10 +63,12 @@ class Pgtk::Impatient
63
63
  # @param [Pgtk::Pool] pool The pool to decorate
64
64
  # @param [Integer] timeout Timeout in seconds for each SQL query
65
65
  # @param [Array<Regex>] off List of regex to exclude queries from checking
66
- def initialize(pool, timeout, *off)
66
+ # @param [Integer] default Fallback timeout in seconds for excluded queries (0 = no timeout)
67
+ def initialize(pool, timeout, *off, default: 300)
67
68
  @pool = pool
68
69
  @timeout = timeout
69
70
  @off = off
71
+ @default = default
70
72
  end
71
73
 
72
74
  # Start a new connection pool with the given arguments.
@@ -86,7 +88,7 @@ class Pgtk::Impatient
86
88
  [
87
89
  @pool.dump,
88
90
  '',
89
- "Pgtk::Impatient (timeout=#{@timeout}s):",
91
+ "Pgtk::Impatient (timeout=#{@timeout}s, default=#{@default}s):",
90
92
  @off.map { |re| " #{re}" }
91
93
  ].join("\n")
92
94
  end
@@ -106,7 +108,13 @@ class Pgtk::Impatient
106
108
  # @raise [TooSlow] If the query takes too long
107
109
  def exec(query, *args)
108
110
  sql = query.is_a?(Array) ? query.join(' ') : query
109
- return @pool.exec(sql, *args) if @off.any? { |re| re.match?(sql) }
111
+ if @off.any? { |re| re.match?(sql) }
112
+ ms = Integer(@default * 1000)
113
+ return @pool.transaction do |t|
114
+ t.exec("SET LOCAL statement_timeout = #{ms}") unless ms.zero?
115
+ t.exec(sql, *args)
116
+ end
117
+ end
110
118
  start = Time.now
111
119
  ms = [Integer(@timeout * 1000), 1].max
112
120
  begin
@@ -129,6 +129,10 @@ class Pgtk::PgsqlTask < Rake::TaskLib
129
129
  "-e POSTGRES_DB=#{Shellwords.escape(@dbname)}",
130
130
  '--detach',
131
131
  '--rm',
132
+ '--health-cmd', 'pg_isready',
133
+ '--health-interval', '1s',
134
+ '--health-timeout', '2s',
135
+ '--health-retries', '10',
132
136
  'postgres:18.1',
133
137
  @config.map { |k, v| "-c #{Shellwords.escape("#{k}=#{v}")}" },
134
138
  stdout:
@@ -148,6 +152,15 @@ class Pgtk::PgsqlTask < Rake::TaskLib
148
152
  rescue WaitUtil::TimeoutError => e
149
153
  raise(IOError, "Failed to start PostgreSQL Docker container #{container.inspect}: #{e.message}")
150
154
  end
155
+ (30 * 2).times do
156
+ status = qbash(
157
+ "docker inspect --format='{{.State.Health.Status}}' #{Shellwords.escape(container)}",
158
+ accept: nil, both: true
159
+ )[0]&.strip
160
+ break if status == 'healthy'
161
+ raise(IOError, "PostgreSQL container #{container.inspect} is #{status}") if status == 'unhealthy'
162
+ sleep(0.5)
163
+ end
151
164
  container
152
165
  end
153
166
 
@@ -181,13 +194,17 @@ class Pgtk::PgsqlTask < Rake::TaskLib
181
194
  puts("PostgreSQL killed in PID #{pid}") unless @quiet
182
195
  end
183
196
  begin
184
- WaitUtil.wait_for_service('PG in local', 'localhost', port, timeout_sec: 10, delay_sec: 0.1)
197
+ WaitUtil.wait_for_service('PG in local', 'localhost', port, timeout_sec: 10, delay_sec: 0.2)
185
198
  rescue WaitUtil::TimeoutError => e
186
199
  puts("+ #{cmd}")
187
200
  puts("stdout:\n#{File.read(File.join(home, 'stdout.txt'))}")
188
201
  puts("stderr:\n#{File.read(File.join(home, 'stderr.txt'))}")
189
202
  raise(IOError, "Failed to start PostgreSQL database server on port #{port}: #{e.message}")
190
203
  end
204
+ (10 * 2).times do
205
+ break if qbash("pg_isready -h localhost -p #{port}", accept: nil, both: true)[1].zero?
206
+ sleep(0.5)
207
+ end
191
208
  qbash(
192
209
  'createdb',
193
210
  '--host', 'localhost',
data/lib/pgtk/pool.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'loog'
4
3
  # SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
5
4
  # SPDX-License-Identifier: MIT
6
5
 
7
6
  require 'ellipsized'
7
+ require 'loog'
8
8
  require 'pg'
9
9
  require 'tago'
10
10
  require_relative '../pgtk'
@@ -73,6 +73,7 @@ class Pgtk::Pool
73
73
  @idle = idle
74
74
  @log = log
75
75
  @pool = IterableQueue.new(max, timeout)
76
+ @lock = Mutex.new
76
77
  @started = false
77
78
  end
78
79
 
@@ -80,7 +81,12 @@ class Pgtk::Pool
80
81
  #
81
82
  # @return [String] Version of PostgreSQL server
82
83
  def version
83
- @version ||= exec('SHOW server_version')[0]['server_version'].split[0]
84
+ @version ||=
85
+ begin
86
+ conn = @pool.pop
87
+ @pool.push(conn)
88
+ conn.parameter_status('server_version').split[0]
89
+ end
84
90
  end
85
91
 
86
92
  # Get as much details about it as possible.
@@ -103,20 +109,22 @@ class Pgtk::Pool
103
109
  # open at the same time. For example, Heroku free PostgreSQL database
104
110
  # allows only one connection open.
105
111
  def start!
106
- return if @started
107
- @max.times do
108
- @pool.push(@wire.connection)
109
- end
110
- (2 * @max).times do
111
- connect { |c| c.exec('SELECT 1') }
112
- rescue StandardError => e
113
- @log.warn("Pool warm-up query failed, slot will be retried: #{e.message.strip}")
114
- end
115
- @max.times do
116
- connect { |c| c.exec('SELECT 1') }
112
+ @lock.synchronize do
113
+ return if @started
114
+ @max.times do
115
+ @pool.push(@wire.connection)
116
+ end
117
+ (2 * @max).times do
118
+ connect { |c| c.exec('SELECT 1') }
119
+ rescue StandardError => e
120
+ @log.warn("Pool warm-up query failed, slot will be retried: #{e.message.strip}")
121
+ end
122
+ @max.times do
123
+ connect { |c| c.exec('SELECT 1') }
124
+ end
125
+ @started = true
126
+ @log.debug("PostgreSQL pool started with #{@max} connections")
117
127
  end
118
- @started = true
119
- @log.debug("PostgreSQL pool started with #{@max} connections")
120
128
  end
121
129
 
122
130
  # Make a query and return the result as an array of hashes. For example,
data/lib/pgtk/spy.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'loog'
4
3
  # SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
5
4
  # SPDX-License-Identifier: MIT
6
5
 
6
+ require 'loog'
7
7
  require 'pg'
8
8
  require_relative '../pgtk'
9
9
  require_relative 'wire'
@@ -82,8 +82,10 @@ class Pgtk::Spy
82
82
  # @param [String] sql The SQL query with params inside (possibly)
83
83
  # @return [Array] Result rows
84
84
  def exec(sql, *)
85
- @block&.call(sql.is_a?(Array) ? sql.join(' ') : sql, Time.now - Time.now)
86
- @pool.exec(sql, *)
85
+ start = Time.now
86
+ @pool.exec(sql, *).tap do
87
+ @block&.call(sql.is_a?(Array) ? sql.join(' ') : sql, Time.now - start)
88
+ end
87
89
  end
88
90
 
89
91
  # Run a transaction with spying on each SQL query.
data/lib/pgtk/stash.rb CHANGED
@@ -29,8 +29,11 @@ require_relative '../pgtk'
29
29
  # Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
30
30
  # License:: MIT
31
31
  class Pgtk::Stash
32
- MODS = %w[INSERT DELETE UPDATE LOCK VACUUM TRANSACTION COMMIT ROLLBACK REINDEX TRUNCATE CREATE ALTER DROP SET].freeze
33
- MODS_RE = Regexp.new("(^|\\s)(#{MODS.join('|')})(\\s|$)")
32
+ MODS = %w[
33
+ INSERT DELETE UPDATE LOCK VACUUM TRANSACTION COMMIT ROLLBACK
34
+ REINDEX TRUNCATE CREATE ALTER DROP SET START
35
+ ].freeze
36
+ MODS_RE = Regexp.new("\\A(#{MODS.join('|')})(\\s|$)")
34
37
 
35
38
  IDENT = '[a-z_][a-z0-9_]*'
36
39
 
@@ -39,9 +42,13 @@ class Pgtk::Stash
39
42
 
40
43
  READS_RE = Regexp.new("(?<=^|\\s)(?:FROM|JOIN)\\s(#{IDENT})(?=\\s|;|$)")
41
44
 
42
- SEPARATOR = ' --%*@#~($-- '
45
+ NONDETERMINISTIC = /
46
+ \b(?:NOW|CURRENT_TIMESTAMP|CURRENT_DATE|CURRENT_TIME|
47
+ LOCALTIMESTAMP|LOCALTIME|RANDOM|GEN_RANDOM_UUID|
48
+ CLOCK_TIMESTAMP)(?:\s*\(|(?=[^a-z0-9_]|$))
49
+ /ix
43
50
 
44
- private_constant :MODS, :ALTS, :IDENT, :MODS_RE, :ALTS_RE, :READS_RE, :SEPARATOR
51
+ private_constant :MODS, :ALTS, :IDENT, :MODS_RE, :ALTS_RE, :READS_RE, :NONDETERMINISTIC
45
52
 
46
53
  # Initialize a new Stash with query caching.
47
54
  #
@@ -76,7 +83,9 @@ class Pgtk::Stash
76
83
  @pool = pool
77
84
  @stash = stash
78
85
  @stash[:table_mod] ||= {}
79
- @stash[:table_inflight] ||= {}
86
+ unless @stash[:table_inflight].default_proc
87
+ @stash[:table_inflight] = Hash.new { |h, k| h[k] = Concurrent::AtomicFixnum.new(0) }
88
+ end
80
89
  @loog = loog
81
90
  @entrance = entrance
82
91
  @refill = refill
@@ -121,8 +130,10 @@ class Pgtk::Stash
121
130
  # @return [PG::Result] Query result object
122
131
  def exec(query, params = [], result = 0)
123
132
  pure = (query.is_a?(Array) ? query.join(' ') : query).gsub(/\s+/, ' ').strip
124
- if MODS_RE.match?(pure) || /(^|\s)pg_[a-z_]+\(/.match?(pure)
133
+ if MODS_RE.match?(pure)
125
134
  modify(pure, params, result)
135
+ elsif /(^|\s)pg_[a-z_]+\(/.match?(pure)
136
+ @pool.exec(pure, params, result)
126
137
  else
127
138
  select(pure, params, result)
128
139
  end
@@ -145,7 +156,7 @@ class Pgtk::Stash
145
156
  {
146
157
  q: q.dup,
147
158
  c: kk.values.count,
148
- p: kk.values.sum { |vv| vv[:popularity] },
159
+ p: kk.values.sum { |vv| vv[:popularity]&.value || 0 },
149
160
  s: kk.values.count { |vv| vv[:stale] },
150
161
  u: kk.values.map { |vv| vv[:used] }.max || Time.now
151
162
  }
@@ -222,15 +233,13 @@ class Pgtk::Stash
222
233
  tables = pure.scan(ALTS_RE).flatten
223
234
  tables.uniq!
224
235
  affected = (tables + tables.flat_map { |t| @cascades&.fetch(t, []) || [] }).uniq
225
- @entrance.with_write_lock do
226
- affected.each { |t| @stash[:table_inflight][t] = (@stash[:table_inflight][t] || 0) + 1 }
227
- end
236
+ affected.each { |t| @stash[:table_inflight][t].increment }
228
237
  begin
229
238
  @pool.exec(pure, params, result).tap do
230
239
  now = Time.now
231
240
  @entrance.with_write_lock do
232
241
  affected.each do |t|
233
- @stash[:table_inflight][t] -= 1
242
+ @stash[:table_inflight][t].decrement
234
243
  @stash[:table_mod][t] = (@stash[:table_mod][t] || 0) + 1
235
244
  @stash[:tables][t]&.each do |q|
236
245
  @stash[:queries][q]&.each_key do |key|
@@ -241,28 +250,25 @@ class Pgtk::Stash
241
250
  end
242
251
  end
243
252
  rescue StandardError
244
- @entrance.with_write_lock do
245
- affected.each { |t| @stash[:table_inflight][t] -= 1 }
246
- end
253
+ affected.each { |t| @stash[:table_inflight][t].decrement }
247
254
  raise
248
255
  end
249
256
  end
250
257
 
251
258
  def select(pure, params, result)
252
- key = params.join(SEPARATOR)
253
- ret = @stash.dig(:queries, pure, key, :ret)
254
- if ret.nil? || @stash.dig(:queries, pure, key, :stale)
259
+ ret = @stash.dig(:queries, pure, params, :ret)
260
+ if ret.nil? || @stash.dig(:queries, pure, params, :stale)
255
261
  tables = pure.scan(READS_RE).flatten
256
262
  tables.uniq!
257
263
  marks = tables.to_h { |t| [t, @stash[:table_mod][t]] }
258
264
  ret = @pool.exec(pure, params, result)
259
- cache(pure, key, params, result, ret, tables, marks) unless pure.include?(' NOW() ')
265
+ cache(pure, params, result, ret, tables, marks) unless pure.match?(NONDETERMINISTIC)
260
266
  end
261
- bump(pure, key) if @stash.dig(:queries, pure, key)
267
+ bump(pure, params) if @stash.dig(:queries, pure, params)
262
268
  ret
263
269
  end
264
270
 
265
- def cache(pure, key, params, result, ret, tables, marks)
271
+ def cache(pure, params, result, ret, tables, marks)
266
272
  raise(ArgumentError, "No tables at #{pure.inspect}") if tables.empty?
267
273
  @entrance.with_write_lock do
268
274
  tables.each do |t|
@@ -270,7 +276,7 @@ class Pgtk::Stash
270
276
  @stash[:tables][t].append(pure).uniq!
271
277
  end
272
278
  @stash[:queries][pure] ||= {}
273
- existing = @stash[:queries][pure][key]
279
+ existing = @stash[:queries][pure][params]
274
280
  stillborn = tables.any? { |t| (cur = @stash[:table_mod][t]) && cur != marks[t] }
275
281
  entry = { ret:, params:, result:, used: Time.now }
276
282
  entry[:stale] =
@@ -280,23 +286,32 @@ class Pgtk::Stash
280
286
  Time.now
281
287
  end
282
288
  entry.delete(:stale) if entry[:stale].nil?
283
- @stash[:queries][pure][key] = entry
289
+ entry[:popularity] = (existing && existing[:popularity]) || Concurrent::AtomicFixnum.new
290
+ @stash[:queries][pure][params] = entry
284
291
  end
285
292
  end
286
293
 
287
- def bump(pure, key)
288
- @entrance.with_write_lock do
289
- @stash[:queries][pure][key][:popularity] ||= 0
290
- @stash[:queries][pure][key][:popularity] += 1
291
- @stash[:queries][pure][key][:used] = Time.now
292
- end
294
+ # Bump popularity and last-used time of a cached entry.
295
+ #
296
+ # This runs without the exclusive write lock: popularity is a
297
+ # +Concurrent::AtomicFixnum+ and +used+ is a plain assignment of an
298
+ # immutable value, neither of which mutates the structure of the
299
+ # +@stash[:queries]+ tree. Keeping it off the writer lock means a
300
+ # read-only workload never serializes through the writer (see #414).
301
+ #
302
+ # @return [void]
303
+ def bump(pure, params)
304
+ entry = @stash.dig(:queries, pure, params)
305
+ return unless entry
306
+ (entry[:popularity] ||= Concurrent::AtomicFixnum.new).increment
307
+ entry[:used] = Time.now
293
308
  end
294
309
 
295
310
  # Calculate total number of cached query results.
296
311
  #
297
312
  # @return [Integer] Total count of cached query results
298
313
  def cached
299
- @entrance.with_write_lock do
314
+ @entrance.with_read_lock do
300
315
  @stash[:queries].values.sum { |kk| kk.values.size }
301
316
  end
302
317
  end
@@ -350,6 +365,8 @@ class Pgtk::Stash
350
365
  end
351
366
  end
352
367
  end
368
+ rescue StandardError => e
369
+ @loog.warn("Stash capper crashed: #{e.class}: #{e.message}")
353
370
  end
354
371
  end
355
372
 
@@ -361,26 +378,30 @@ class Pgtk::Stash
361
378
  evict(q) if @stash[:queries][q].empty?
362
379
  end
363
380
  end
381
+ rescue StandardError => e
382
+ @loog.warn("Stash retiree crashed: #{e.class}: #{e.message}")
364
383
  end
365
384
  end
366
385
 
367
- def evict(query)
368
- @stash[:queries].delete(query)
369
- @stash[:tables].each_value { |list| list.delete(query) }
370
- @stash[:tables].delete_if { |_, list| list.empty? }
371
- end
372
-
373
386
  def refiller!
374
387
  Concurrent::TimerTask.execute(execution_interval: @refill, executor: @tpool) do
375
388
  ranked.each { |q| replenish(q) }
389
+ rescue StandardError => e
390
+ @loog.warn("Stash refiller crashed: #{e.class}: #{e.message}")
376
391
  end
377
392
  end
378
393
 
394
+ def evict(query)
395
+ @stash[:queries].delete(query)
396
+ @stash[:tables].each_value { |list| list.delete(query) }
397
+ @stash[:tables].delete_if { |_, list| list.empty? }
398
+ end
399
+
379
400
  def ranked
380
401
  qq =
381
402
  @entrance.with_write_lock do
382
403
  @stash[:queries]
383
- .map { |k, v| [k, v.values.sum { |vv| vv[:popularity] }, v.values.any? { |vv| vv[:stale] }] }
404
+ .map { |k, v| [k, v.values.sum { |vv| vv[:popularity]&.value || 0 }, v.values.any? { |vv| vv[:stale] }] }
384
405
  end
385
406
  qq.select { _1[2] }.sort_by { -_1[1] }.map { _1[0] }
386
407
  end
@@ -408,7 +429,7 @@ class Pgtk::Stash
408
429
  next unless h
409
430
  next unless h[:stale] == mark
410
431
  next if pinned.any? { |t, m| @stash[:table_mod][t] != m }
411
- next if tables.any? { |t| (@stash[:table_inflight][t] || 0).positive? }
432
+ next if tables.any? { |t| @stash[:table_inflight][t].value.positive? }
412
433
  h[:ret] = ret
413
434
  h.delete(:stale)
414
435
  end
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.32.5' unless defined?(VERSION)
13
+ VERSION = '0.32.6' unless defined?(VERSION)
14
14
  end
data/lib/pgtk/wire/env.rb CHANGED
@@ -29,7 +29,7 @@ class Pgtk::Wire::Env
29
29
  def initialize(var = 'DATABASE_URL', **opts)
30
30
  raise(ArgumentError, "The name of the environment variable can't be nil") if var.nil?
31
31
  @value = ENV.fetch(var, nil)
32
- raise(ArgumentError, "The environment variable #{@value.inspect} is not set") if @value.nil?
32
+ raise(ArgumentError, "The environment variable #{var.inspect} is not set") if @value.nil?
33
33
  @opts = opts
34
34
  end
35
35
 
@@ -19,11 +19,14 @@ class Pgtk::Wire::Yaml
19
19
  #
20
20
  # @param [String] file Path to the YAML configuration file
21
21
  # @param [String] node The root node name in the YAML file containing PostgreSQL configuration
22
- def initialize(file, node = 'pgsql')
22
+ # @param [Hash] opts Extra options forwarded to +PG.connect+ (e.g. +sslmode+,
23
+ # +connect_timeout+, +keepalives+). These override values from the YAML file.
24
+ def initialize(file, node = 'pgsql', **opts)
23
25
  raise(ArgumentError, "The name of the file can't be nil") if file.nil?
24
26
  @file = file
25
27
  raise(ArgumentError, "The name of the node in the YAML file can't be nil") if node.nil?
26
28
  @node = node
29
+ @opts = opts
27
30
  end
28
31
 
29
32
  # Create a new connection to PostgreSQL server.
@@ -36,7 +39,9 @@ class Pgtk::Wire::Yaml
36
39
  port: cfg[@node]['port'],
37
40
  dbname: cfg[@node]['dbname'],
38
41
  user: cfg[@node]['user'],
39
- password: cfg[@node]['password']
42
+ password: cfg[@node]['password'],
43
+ **cfg[@node].except(*%w[host port dbname user password url]).transform_keys(&:to_sym),
44
+ **@opts
40
45
  ).connection
41
46
  end
42
47
  end
data/pgtk.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
27
27
  s.rdoc_options = ['--charset=UTF-8']
28
28
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
29
- s.add_dependency('backtrace', '~>0.4')
29
+ s.add_dependency('cgi', '~>0.0')
30
30
  s.add_dependency('concurrent-ruby', '~>1.3')
31
31
  s.add_dependency('donce', '~>0.0')
32
32
  s.add_dependency('ellipsized', '~>0.3')
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.11</postgresql.version>
13
+ <postgresql.version>42.7.12</postgresql.version>
14
14
  <liquibase.version>5.0.3</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.32.5
4
+ version: 0.32.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -10,19 +10,19 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: backtrace
13
+ name: cgi
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.4'
18
+ version: '0.0'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.4'
25
+ version: '0.0'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: concurrent-ruby
28
28
  requirement: !ruby/object:Gem::Requirement