pgtk 0.33.0 → 0.34.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7acb299c9e4e6cd1e272651041704fd89da2858bd1e4b2aa935eed9724815419
4
- data.tar.gz: 1260a6fb42a11e34d1a733eb9d1bff8ba6a28f81724457e12986adccd134ebc5
3
+ metadata.gz: f4e1d913f72c6d33b8ecdb1cc17487c2ad7dfceae06f897b81cf0b4c12d32278
4
+ data.tar.gz: 456a7e3ae4a04891f3c51c0bc32108a30625782db57cca1725dbc4aec20b9363
5
5
  SHA512:
6
- metadata.gz: 29aad3a51e77675418fc5eaffa72d7e0015d4e6d1464c87cc118f3ab94e223b0ce4461cffba91e7f4e2d22b594bb0a61f2d4b299db1055d2637d9afca5a0533d
7
- data.tar.gz: 8483ec44d6da87cd0e57f57e2ca14373965269a5463bc74ba7e38ac4693b4acf35299a64b6ec91a17fc04812160ca31e6826313fce660cdd3e210c8cc3007c81
6
+ metadata.gz: 810a9b990e6e040683f02f58932ce480aa250331d1a73c6750e68f9ede1b069a0a63446ce3ecc3971bf07cb5695f2c25ce64d697fe33a4d1d5be18ed5d7b7ae5
7
+ data.tar.gz: c2ac270f074f32bab3ce1dc03b7a68b9c5ae17eeccc764d07e379813a665d57b968f6f2de672b9640db31d56e716a7212813e95d6968d8988ff4fd25e375d898
data/Gemfile.lock CHANGED
@@ -36,8 +36,8 @@ GEM
36
36
  tago (~> 0.1)
37
37
  ellipsized (0.3.0)
38
38
  joined (0.4.0)
39
- json (2.20.0)
40
- language_server-protocol (3.17.0.5)
39
+ json (2.21.1)
40
+ language_server-protocol (3.17.0.6)
41
41
  lint_roller (1.1.0)
42
42
  logger (1.7.0)
43
43
  loog (0.8.0)
@@ -71,7 +71,7 @@ GEM
71
71
  racc (~> 1.4)
72
72
  os (1.1.4)
73
73
  parallel (2.1.0)
74
- parser (3.3.11.1)
74
+ parser (3.3.12.0)
75
75
  ast (~> 2.4.1)
76
76
  racc
77
77
  pg (1.6.3)
@@ -95,7 +95,7 @@ GEM
95
95
  tago (~> 0.0)
96
96
  regexp_parser (2.12.0)
97
97
  rexml (3.4.4)
98
- rubocop (1.88.1)
98
+ rubocop (1.88.2)
99
99
  json (~> 2.3)
100
100
  language_server-protocol (~> 3.17.0.2)
101
101
  lint_roller (~> 1.1.0)
@@ -106,13 +106,13 @@ GEM
106
106
  rubocop-ast (>= 1.49.0, < 2.0)
107
107
  ruby-progressbar (~> 1.7)
108
108
  unicode-display_width (>= 2.4.0, < 4.0)
109
- rubocop-ast (1.49.1)
109
+ rubocop-ast (1.50.0)
110
110
  parser (>= 3.3.7.2)
111
111
  prism (~> 1.7)
112
- rubocop-elegant (0.7.0)
112
+ rubocop-elegant (0.7.1)
113
113
  lint_roller (~> 1.1)
114
114
  rubocop (~> 1.75)
115
- rubocop-minitest (0.39.1)
115
+ rubocop-minitest (0.40.0)
116
116
  lint_roller (~> 1.1)
117
117
  rubocop (>= 1.75.0, < 2.0)
118
118
  rubocop-ast (>= 1.38.0, < 2.0)
@@ -148,7 +148,7 @@ GEM
148
148
  nokogiri (~> 1.8)
149
149
  rainbow (~> 3.0)
150
150
  slop (~> 4.4)
151
- yard (0.9.44)
151
+ yard (0.9.45)
152
152
 
153
153
  PLATFORMS
154
154
  aarch64-linux
data/README.md CHANGED
@@ -217,14 +217,26 @@ end
217
217
  ## Query Timeouts with `Pgtk::Impatient`
218
218
 
219
219
  To prevent queries from running indefinitely, use `Pgtk::Impatient` to enforce
220
- timeouts on database operations:
220
+ timeouts on database operations.
221
+ The limit itself belongs to the connection: the wire puts `statement_timeout`
222
+ into the startup packet, and the decorator explains what happened when
223
+ PostgreSQL applies it:
221
224
 
222
225
  ```ruby
223
226
  require 'pgtk/impatient'
224
- # Wrap the pool with a 5-second timeout for all queries
227
+ # Every connection of the pool carries a 5-second limit
228
+ pool = Pgtk::Pool.new(
229
+ Pgtk::Wire::Env.new('DATABASE_URL', options: '-c statement_timeout=5000'),
230
+ max: 4
231
+ )
232
+ pool.start!
233
+ # Wrap the pool, telling it about the very same 5 seconds
225
234
  impatient = Pgtk::Impatient.new(pool, 5)
226
235
  ```
227
236
 
237
+ Both numbers must agree, since the connection stops the query, while the
238
+ decorator only reports it.
239
+
228
240
  The impatient decorator ensures queries don't hang your application:
229
241
 
230
242
  ```ruby
@@ -243,31 +255,32 @@ You can exclude specific queries from timeout enforcement using regex patterns:
243
255
  impatient = Pgtk::Impatient.new(pool, 2, /^SELECT/, /^VACUUM/)
244
256
  ```
245
257
 
246
- Excluded queries are never wrapped in a transaction, because statements such as
247
- `VACUUM`, `REINDEX`, or `CREATE INDEX CONCURRENTLY` cannot run inside a
248
- transaction block. Instead, a longer fallback timeout (300 seconds by default,
249
- configurable via the `default:` argument) is applied at the session level with
250
- `SET statement_timeout` before the query runs, and reset afterwards. Pass
251
- `default: 0` to run excluded queries with no timeout at all:
258
+ Excluded queries get a longer fallback timeout (300 seconds by default,
259
+ configurable via the `default:` argument), applied at the session level with
260
+ `SET statement_timeout` before the query runs and reset afterwards. This is how
261
+ statements that legitimately need much longer than a query, such as `VACUUM`,
262
+ `REINDEX`, or `CREATE INDEX CONCURRENTLY`, stay possible. Pass `default: 0` to
263
+ run excluded queries with no timeout at all:
252
264
 
253
265
  ```ruby
254
266
  # Give excluded queries a 60-second fallback timeout
255
267
  impatient = Pgtk::Impatient.new(pool, 2, /^VACUUM/, default: 60)
256
268
  ```
257
269
 
258
- The timeout is enforced on the server side: each query is wrapped in a tiny
259
- transaction that issues `SET LOCAL statement_timeout`, and PostgreSQL itself
260
- terminates the query at the deadline. This guarantees the server-side
261
- connection slot is freed even when the client cannot deliver a cancellation
262
- request for example, behind a transaction-pool PgBouncer that does not
263
- forward client disconnects to in-flight server queries.
270
+ The limit travels in the startup packet and nowhere else. An in-band
271
+ `SET LOCAL statement_timeout` arrives behind a `START TRANSACTION`, and both of
272
+ them run with no limit in force, which is exactly the hang that `Impatient`
273
+ exists to prevent. A setting from the startup packet is in force from the first
274
+ byte of the first statement, survives a transaction-pooling PgBouncer, and
275
+ frees the server-side connection slot even when the client cannot deliver a
276
+ cancellation request.
264
277
 
265
278
  Key features:
266
279
 
267
280
  1. Configurable timeout in seconds for each query
268
281
  1. Raises `Pgtk::Impatient::TooSlow` exception when timeout is exceeded
269
282
  1. Can exclude queries matching specific patterns from timeout checks
270
- 1. Sets PostgreSQL's `statement_timeout` per query and per transaction,
283
+ 1. Sends each query in a single round trip, with the limit already in force,
271
284
  so timeouts are enforced server-side and orphan backends do not pile up
272
285
 
273
286
  ## Query Caching with `Pgtk::Stash`
@@ -8,9 +8,9 @@ require_relative '../../pgtk'
8
8
  class Pgtk::Impatient; end
9
9
 
10
10
  # Raised by Pgtk::Impatient#exec when the query takes longer than the
11
- # configured timeout. The deadline is enforced server-side via
12
- # +SET LOCAL statement_timeout+, so the underlying +PG::QueryCanceled+
13
- # is translated into this error for the caller.
11
+ # configured timeout. The deadline is enforced server-side, by the
12
+ # +statement_timeout+ of the connection, so the underlying
13
+ # +PG::QueryCanceled+ is translated into this error for the caller.
14
14
  #
15
15
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
16
16
  # Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
@@ -12,26 +12,30 @@ require_relative '../pgtk'
12
12
  # It ensures that SQL queries don't run indefinitely, which helps prevent application
13
13
  # hangs and resource exhaustion when database operations are slow or stalled.
14
14
  #
15
- # This class implements the same interface as Pool but enforces the timeout on the
16
- # server side, by wrapping each query in a tiny transaction that issues
17
- # +SET LOCAL statement_timeout+. PostgreSQL itself terminates the query at the
18
- # deadline, which guarantees that the server-side connection slot is freed even
19
- # when the client cannot deliver a cancellation request (for example, behind a
20
- # transaction-pool PgBouncer that does not forward client disconnects to in-flight
21
- # server queries). On timeout, +TooSlow+ is raised.
15
+ # This class implements the same interface as Pool. The timeout itself is
16
+ # enforced by PostgreSQL, through the +statement_timeout+ that the wire puts
17
+ # into the startup packet of every connection. The limit is in force from the
18
+ # first byte of the first statement, which no in-band +SET+ can achieve: the
19
+ # +SET+ itself, and the +START TRANSACTION+ ahead of it, would travel with no
20
+ # limit at all. Because the setting arrives in the startup packet, it also
21
+ # survives a transaction-pooling PgBouncer, and it frees the server-side
22
+ # connection slot even when the client cannot deliver a cancellation request.
23
+ # When PostgreSQL terminates a query at the deadline, +TooSlow+ is raised here.
22
24
  #
23
25
  # Queries that match one of the +off+ regular expressions are excluded from
24
- # this checking. They are never wrapped in a transaction, because some
25
- # statements (such as +VACUUM+, +REINDEX+, or +CREATE INDEX CONCURRENTLY+)
26
- # cannot run inside a transaction block. Instead, a session-level
27
- # +SET statement_timeout+ is applied on the same connection before the query
28
- # runs (and reset afterwards), using the +default+ fallback timeout. Pass
29
- # +default: 0+ to run excluded queries with no timeout at all.
26
+ # this checking. They run on a single connection, guarded by a session-level
27
+ # +SET statement_timeout+ with the +default+ fallback timeout, which is reset
28
+ # afterwards. Pass +default: 0+ to run excluded queries with no timeout at all.
29
+ # This is how statements that take much longer than a query, such as +VACUUM+
30
+ # or +REINDEX+, stay possible.
30
31
  #
31
32
  # Basic usage:
32
33
  #
33
- # # Create and configure a regular pool
34
- # pool = Pgtk::Pool.new(wire, max: 4)
34
+ # # Create a pool of connections that carry a two-second limit
35
+ # pool = Pgtk::Pool.new(
36
+ # Pgtk::Wire::Env.new('DATABASE_URL', options: '-c statement_timeout=2000'),
37
+ # max: 4
38
+ # )
35
39
  # pool.start!
36
40
  #
37
41
  # # Wrap the pool in an impatient decorator with a 2-second timeout
@@ -69,7 +73,8 @@ class Pgtk::Impatient
69
73
  # Constructor.
70
74
  #
71
75
  # @param [Pgtk::Pool] pool The pool to decorate
72
- # @param [Integer] timeout Timeout in seconds for each SQL query
76
+ # @param [Integer] timeout Timeout in seconds for each SQL query, the same
77
+ # one that the connections of the pool carry as +statement_timeout+
73
78
  # @param [Array<Regex>] off List of regex to exclude queries from checking
74
79
  # @param [Integer] default Fallback timeout in seconds for excluded queries (0 = no timeout)
75
80
  def initialize(pool, timeout, *off, default: 300)
@@ -103,18 +108,14 @@ class Pgtk::Impatient
103
108
 
104
109
  # Execute a SQL query with a server-side timeout.
105
110
  #
106
- # The query is wrapped in a tiny transaction that issues
107
- # +SET LOCAL statement_timeout+, so PostgreSQL itself terminates the query
108
- # at the deadline. This guarantees the server-side connection slot is freed
109
- # even when the client cannot deliver a cancellation request (for example,
110
- # behind a transaction-pool PgBouncer). When the deadline fires, the
111
- # underlying +PG::QueryCanceled+ is translated to +TooSlow+.
111
+ # The query travels alone, in a single round trip, and the limit is the
112
+ # +statement_timeout+ that the connection already carries. When the deadline
113
+ # fires, the underlying +PG::QueryCanceled+ is translated to +TooSlow+.
112
114
  #
113
- # Queries matching one of the +off+ regular expressions bypass this
114
- # transaction. They run on a single connection without a transaction block,
115
- # guarded by a session-level +SET statement_timeout+ (the +default+ fallback,
116
- # reset afterwards) or by no timeout at all when +default+ is zero. This keeps
117
- # statements that cannot run inside a transaction, such as +VACUUM+ or
115
+ # Queries matching one of the +off+ regular expressions are guarded instead
116
+ # by a session-level +SET statement_timeout+ (the +default+ fallback, reset
117
+ # afterwards), or by no timeout at all when +default+ is zero. This keeps
118
+ # statements that need much longer than a query, such as +VACUUM+ or
118
119
  # +REINDEX+, working as expected.
119
120
  #
120
121
  # @param [String, Array] query The SQL query with params inside (possibly)
@@ -124,20 +125,14 @@ class Pgtk::Impatient
124
125
  def exec(query, *args)
125
126
  sql = query.is_a?(Array) ? query.join(' ') : query
126
127
  if @off.any? { |re| re.match?(sql) }
127
- ms = Integer(@default * 1000)
128
- return @pool.exec(sql, *args) if ms.zero?
129
128
  return @pool.session do |t|
130
- t.exec("SET statement_timeout = #{ms}")
129
+ t.exec("SET statement_timeout = #{Integer(@default * 1000)}")
131
130
  t.exec(sql, *args).tap { t.exec('RESET statement_timeout') }
132
131
  end
133
132
  end
134
133
  start = Time.now
135
- ms = [Integer(@timeout * 1000), 1].max
136
134
  begin
137
- @pool.transaction do |t|
138
- t.exec("SET LOCAL statement_timeout = #{ms}")
139
- t.exec(sql, *args)
140
- end
135
+ @pool.exec(sql, *args)
141
136
  rescue PG::QueryCanceled
142
137
  raise(
143
138
  TooSlow, [
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.33.0' unless defined?(VERSION)
13
+ VERSION = '0.34.0' unless defined?(VERSION)
14
14
  end
data/resources/pom.xml CHANGED
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <!--
3
- * SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
- * SPDX-License-Identifier: MIT
3
+ * SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
+ * SPDX-License-Identifier: MIT
5
5
  -->
6
6
  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
7
7
  <modelVersion>4.0.0</modelVersion>
@@ -10,7 +10,7 @@
10
10
  <version>0.0.0</version>
11
11
  <packaging>pom</packaging>
12
12
  <properties>
13
- <postgresql.version>42.7.12</postgresql.version>
13
+ <postgresql.version>42.7.13</postgresql.version>
14
14
  <liquibase.version>5.0.3</liquibase.version>
15
15
  </properties>
16
16
  <dependencies>
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <!--
3
- * SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
- * SPDX-License-Identifier: MIT
3
+ * SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
+ * SPDX-License-Identifier: MIT
5
5
  -->
6
6
  <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd" logicalFilePath="001-initial-schema.xml">
7
7
  <changeSet id="01" author="yegor256">
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <!--
3
- * SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
- * SPDX-License-Identifier: MIT
3
+ * SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
+ * SPDX-License-Identifier: MIT
5
5
  -->
6
6
  <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
7
7
  <includeAll path="2019"/>
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.33.0
4
+ version: 0.34.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko