activerecord-pg-extensions 0.3.0 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b240f7e9306ba57d7f52add8cef37ebe735e2f3d900d328d7cfb2e22fe1fcc6
4
- data.tar.gz: 04b62ecf9b4c250e2f664f1a0be0c71baac1b6f5d117dc8d45f08bb7b7100459
3
+ metadata.gz: 33e9819bdc74cc2783fd5a98746354d8ab13295e66c239eb04bb240b365b829d
4
+ data.tar.gz: 4d93cb57f18d3becc1b988ab1aa3b4e6b98959649642583d5d0a5fbb09c3eb5a
5
5
  SHA512:
6
- metadata.gz: c5a1ae9925eb093ea81424574d409186c85ef2474b1cc2a38f388757ff4e94a8b3893ca7e582e5010e8dbe8915a2e499d1600103c12936169d0447703b5c8d68
7
- data.tar.gz: 256b8740a417e1bcc84a7edd23663da83543aa412a7221ae2a818721dd04a16b7ccf3f06915cb625d50f95ecb55ccd0719e1d8204441294dd9860ba25cb54b6a
6
+ metadata.gz: 42b9849e30f65df38b23e3ce1a9290875037bf4f2af7489f30f18ae9e242440c9dbbe98e35017599d85ac5431858044db346f83a972ffea6af7000e9fdeba67c
7
+ data.tar.gz: b472403821b0b83a5e9e5010a01bd9677b3af11e9cadaac9b7bc78570fcb65a2b746654075ddca60c4505092255269f6af78d801ba1758a1b5c8d295e156fff0
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
@@ -12,7 +12,8 @@ module ActiveRecord
12
12
  return super if nullness != false || open_transactions.positive?
13
13
  return if columns(table).find { |c| c.name == column.to_s }&.null == false
14
14
 
15
- temp_constraint_name = "chk_rails_#{table}_#{column}_not_null"
15
+ # PG identifiers max out at 63 characters
16
+ temp_constraint_name = "chk_rails_#{table}_#{column}_not_null"[0...63]
16
17
  scope = quoted_scope(temp_constraint_name)
17
18
  # check for temp constraint
18
19
  valid = select_value(<<~SQL, "SCHEMA")
@@ -236,15 +236,41 @@ module ActiveRecord
236
236
  select_value("SELECT pg_is_in_recovery()")
237
237
  end
238
238
 
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
239
268
  def with_statement_timeout(timeout = nil)
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 * 1000).to_i}ms", 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 * 1000).to_i}ms", 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.3"
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 '30000ms'",
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 '5500ms'",
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 '5000ms'",
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 '30000ms'",
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 '30000ms'",
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 '30000ms'",
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 '30000ms'",
414
+ "SAVEPOINT active_record_1",
415
+ "SET LOCAL statement_timeout TO '15000ms'",
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.3
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: 2022-02-11 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
@@ -205,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
206
  - !ruby/object:Gem::Version
206
207
  version: '0'
207
208
  requirements: []
208
- rubygems_version: 3.2.24
209
+ rubygems_version: 3.1.4
209
210
  signing_key:
210
211
  specification_version: 4
211
212
  summary: Several extensions to ActiveRecord's PostgreSQLAdapter.