activerecord-pg-extensions 0.3.0 → 0.4.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: 3b240f7e9306ba57d7f52add8cef37ebe735e2f3d900d328d7cfb2e22fe1fcc6
4
- data.tar.gz: 04b62ecf9b4c250e2f664f1a0be0c71baac1b6f5d117dc8d45f08bb7b7100459
3
+ metadata.gz: 3494201ce01b5c6d5104ae9ddb7f993310223ecc94bd902b2484ce6b6a4e525b
4
+ data.tar.gz: acd5b4c842cbfc275b2a86f009c048e7916709803f3d72c9ed40f42095c47a7d
5
5
  SHA512:
6
- metadata.gz: c5a1ae9925eb093ea81424574d409186c85ef2474b1cc2a38f388757ff4e94a8b3893ca7e582e5010e8dbe8915a2e499d1600103c12936169d0447703b5c8d68
7
- data.tar.gz: 256b8740a417e1bcc84a7edd23663da83543aa412a7221ae2a818721dd04a16b7ccf3f06915cb625d50f95ecb55ccd0719e1d8204441294dd9860ba25cb54b6a
6
+ metadata.gz: 91625c727722fb91aa1860960a3f9c5ba7ba4dcab0a0cf459e6b8301a7fa734ff91882f5ae31ac4cb492218eaf17205ed97e54236cf8760d55b6c33b4cd3f7dc
7
+ data.tar.gz: fbe3e4a073f7f646438b07f1226e5c654a744c3efb421ea38b9c7b7adc5cf9e173fbab479780f765cc41f3c9144294d1ac16fe7f140e68bcdf44128a0bcafd68
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2021-09-29
4
+
5
+ - Deprecate with_statement_timeout
6
+ - Add statement_timeout, lock_timeout, and idle_in_transaction_session_timeout
3
7
  ## [0.2.3] - 2021-06-23
4
8
 
5
9
  - Fix extension_available?
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- class QueryTimeout < ActiveRecord::StatementInvalid; end
4
+ # @deprecated just use QueryCanceled
5
+ QueryTimeout = QueryCanceled
5
6
  end
@@ -236,15 +236,41 @@ module ActiveRecord
236
236
  select_value("SELECT pg_is_in_recovery()")
237
237
  end
238
238
 
239
- def with_statement_timeout(timeout = nil)
239
+ def set(configuration_parameter, value, local: false)
240
+ value = value.nil? ? "DEFAULT" : quote(value)
241
+ execute("SET#{' LOCAL' if local} #{configuration_parameter} TO #{value}")
242
+ end
243
+
244
+ def reset(configuration_parameter)
245
+ execute("RESET #{configuration_parameter}")
246
+ end
247
+
248
+ TIMEOUTS = %i[lock_timeout statement_timeout idle_in_transaction_session_timeout].freeze
249
+
250
+ TIMEOUTS.each do |kind|
251
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
252
+ def #{kind}
253
+ current_transaction.#{kind}
254
+ end
255
+
256
+ def #{kind}=(timeout)
257
+ raise ArgumentError, "Timeouts can only be set inside of a transaction" unless current_transaction.open?
258
+
259
+ current_transaction.send(:#{kind}=, timeout)
260
+ end
261
+ RUBY
262
+ end
263
+
264
+ # @deprecated: manage the transaction yourself and set statement_timeout directly
265
+ #
266
+ # otherwise, if you're already in a transaction, or you nest with_statement_timeout,
267
+ # the value will unexpectedly "stick" even after the block returns
268
+ def with_statement_timeout(timeout)
240
269
  timeout = 30 if timeout.nil? || timeout == true
270
+
241
271
  transaction do
242
- execute("SET LOCAL statement_timeout=#{(timeout * 1000).to_i}")
272
+ self.statement_timeout = timeout
243
273
  yield
244
- rescue ActiveRecord::StatementInvalid => e
245
- raise ActiveRecord::QueryTimeout.new(sql: e.sql, binds: e.binds) if e.cause.is_a?(PG::QueryCanceled)
246
-
247
- raise
248
274
  end
249
275
  end
250
276
 
@@ -10,8 +10,11 @@ module ActiveRecord
10
10
  ActiveSupport.on_load(:active_record) do
11
11
  require "active_record/pg_extensions/errors"
12
12
  require "active_record/pg_extensions/postgresql_adapter"
13
+ require "active_record/pg_extensions/transaction"
13
14
 
14
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapter)
15
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapter)
16
+ ::ActiveRecord::ConnectionAdapters::NullTransaction.prepend(NullTransaction)
17
+ ::ActiveRecord::ConnectionAdapters::Transaction.prepend(Transaction)
15
18
  # if they've already require 'all', then inject now
16
19
  defined?(All) && All.inject
17
20
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module PGExtensions
5
+ # Contains general additions to Transaction
6
+ module Transaction
7
+ PostgreSQLAdapter::TIMEOUTS.each do |kind|
8
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
9
+ def #{kind}(local: false)
10
+ return @#{kind} if local
11
+ @#{kind} || parent_transaction.#{kind}
12
+ end
13
+
14
+ private def #{kind}=(timeout)
15
+ return if @#{kind} == timeout
16
+
17
+ @#{kind} = timeout
18
+ return unless materialized?
19
+ connection.set(#{kind.inspect}, "\#{timeout}s", local: true)
20
+ end
21
+ RUBY
22
+ end
23
+
24
+ def materialize!
25
+ PostgreSQLAdapter::TIMEOUTS.each do |kind|
26
+ next if (timeout = send(kind, local: true)).nil?
27
+
28
+ connection.set(kind, "#{timeout}s", local: true)
29
+ end
30
+ super
31
+ end
32
+ end
33
+
34
+ # Contains general additions to NullTransaction
35
+ module NullTransaction
36
+ PostgreSQLAdapter::TIMEOUTS.each do |kind|
37
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
38
+ def #{kind}
39
+ nil
40
+ end
41
+ RUBY
42
+ end
43
+ end
44
+ end
45
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module PGExtensions
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
@@ -270,12 +270,20 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
270
270
  end
271
271
 
272
272
  describe "#with_statement_timeout" do
273
+ around do |example|
274
+ # these specs were written before we supported deferring setting timeouts
275
+ # until the transaction materializes
276
+ connection.disable_lazy_transactions!
277
+ example.call
278
+ connection.enable_lazy_transactions!
279
+ end
280
+
273
281
  it "stops long-running queries" do
274
282
  expect do
275
283
  connection.with_statement_timeout(0.01) do
276
284
  connection.execute("SELECT pg_sleep(3)")
277
285
  end
278
- end.to raise_error(ActiveRecord::QueryTimeout)
286
+ end.to raise_error(ActiveRecord::QueryCanceled)
279
287
  end
280
288
 
281
289
  it "re-raises other errors" do
@@ -283,16 +291,20 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
283
291
  connection.with_statement_timeout(1) do
284
292
  connection.execute("bad sql")
285
293
  end
286
- end.to raise_error(ActiveRecord::StatementInvalid)
294
+ end.to(raise_error { |e| expect(e.cause).to be_a(PG::SyntaxError) })
287
295
  end
288
296
 
289
297
  context "without executing" do
298
+ around do |example|
299
+ connection.dont_execute(&example)
300
+ end
301
+
290
302
  it "converts integer to ms" do
291
303
  connection.with_statement_timeout(30) { nil }
292
304
  expect(connection.executed_statements).to eq(
293
305
  [
294
306
  "BEGIN",
295
- "SET LOCAL statement_timeout=30000",
307
+ "SET LOCAL statement_timeout TO '30s'",
296
308
  "COMMIT"
297
309
  ]
298
310
  )
@@ -303,7 +315,7 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
303
315
  expect(connection.executed_statements).to eq(
304
316
  [
305
317
  "BEGIN",
306
- "SET LOCAL statement_timeout=5500",
318
+ "SET LOCAL statement_timeout TO '5.5s'",
307
319
  "COMMIT"
308
320
  ]
309
321
  )
@@ -314,7 +326,7 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
314
326
  expect(connection.executed_statements).to eq(
315
327
  [
316
328
  "BEGIN",
317
- "SET LOCAL statement_timeout=5000",
329
+ "SET LOCAL statement_timeout TO '5s'",
318
330
  "COMMIT"
319
331
  ]
320
332
  )
@@ -325,7 +337,7 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
325
337
  expect(connection.executed_statements).to eq(
326
338
  [
327
339
  "BEGIN",
328
- "SET LOCAL statement_timeout=30000",
340
+ "SET LOCAL statement_timeout TO '30s'",
329
341
  "COMMIT"
330
342
  ]
331
343
  )
@@ -333,6 +345,81 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
333
345
  end
334
346
  end
335
347
 
348
+ describe "#statement_timeout=" do
349
+ around do |example|
350
+ connection.dont_execute(&example)
351
+ end
352
+
353
+ it "raises if a transaction isn't active" do
354
+ expect { connection.statement_timeout = 30 }.to raise_error(ArgumentError)
355
+ end
356
+
357
+ it "does nothing if the transaction never materializes" do
358
+ connection.transaction do
359
+ connection.statement_timeout = 30
360
+ expect(connection.statement_timeout).to eq 30
361
+ end
362
+ expect(connection.statement_timeout).to be_nil
363
+
364
+ expect(connection.executed_statements).to be_empty
365
+ end
366
+
367
+ it "sets the timeout if the transaction is materialized" do
368
+ connection.transaction do
369
+ connection.select_value("SELECT 1")
370
+ connection.statement_timeout = 30
371
+ expect(connection.statement_timeout).to eq 30
372
+ end
373
+ expect(connection.statement_timeout).to be_nil
374
+
375
+ expect(connection.executed_statements).to eq(
376
+ ["BEGIN",
377
+ "SELECT 1",
378
+ "SET LOCAL statement_timeout TO '30s'",
379
+ "COMMIT"]
380
+ )
381
+ end
382
+
383
+ it "sets the timeout if the transaction materializes" do
384
+ connection.transaction do
385
+ connection.statement_timeout = 30
386
+ connection.select_value("SELECT 1")
387
+ expect(connection.statement_timeout).to eq 30
388
+ end
389
+ expect(connection.statement_timeout).to be_nil
390
+
391
+ expect(connection.executed_statements).to eq(
392
+ ["BEGIN",
393
+ "SET LOCAL statement_timeout TO '30s'",
394
+ "SELECT 1",
395
+ "COMMIT"]
396
+ )
397
+ end
398
+
399
+ it "works with nested transactions" do
400
+ connection.transaction do
401
+ connection.statement_timeout = 30
402
+ connection.transaction(requires_new: true) do
403
+ connection.statement_timeout = 15
404
+ connection.select_value("SELECT 1")
405
+ expect(connection.statement_timeout).to eq 15
406
+ end
407
+ expect(connection.statement_timeout).to eq 30
408
+ end
409
+ expect(connection.statement_timeout).to be_nil
410
+
411
+ expect(connection.executed_statements).to eq(
412
+ ["BEGIN",
413
+ "SET LOCAL statement_timeout TO '30s'",
414
+ "SAVEPOINT active_record_1",
415
+ "SET LOCAL statement_timeout TO '15s'",
416
+ "SELECT 1",
417
+ "RELEASE SAVEPOINT active_record_1",
418
+ "COMMIT"]
419
+ )
420
+ end
421
+ end
422
+
336
423
  unless Rails.version >= "6.1"
337
424
  describe "#add_check_constraint" do
338
425
  around do |example|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-pg-extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-09 00:00:00.000000000 Z
11
+ date: 2021-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -180,6 +180,7 @@ files:
180
180
  - lib/active_record/pg_extensions/pessimistic_migrations.rb
181
181
  - lib/active_record/pg_extensions/postgresql_adapter.rb
182
182
  - lib/active_record/pg_extensions/railtie.rb
183
+ - lib/active_record/pg_extensions/transaction.rb
183
184
  - lib/active_record/pg_extensions/version.rb
184
185
  - lib/activerecord-pg-extensions.rb
185
186
  - spec/pessimistic_migrations_spec.rb