activerecord-pg-extensions 0.2.2 → 0.4.1
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/active_record/pg_extensions/errors.rb +6 -0
- data/lib/active_record/pg_extensions/pessimistic_migrations.rb +25 -9
- data/lib/active_record/pg_extensions/postgresql_adapter.rb +54 -1
- data/lib/active_record/pg_extensions/railtie.rb +5 -1
- data/lib/active_record/pg_extensions/transaction.rb +45 -0
- data/lib/active_record/pg_extensions/version.rb +1 -1
- data/spec/pessimistic_migrations_spec.rb +47 -18
- data/spec/postgresql_adapter_spec.rb +204 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca67149b2f8917743b8258c14b7d2e393673093d02956a192ce40ed7857efa89
|
4
|
+
data.tar.gz: df37221b1ba08633bcb1d059ae296c4f83ec2025e6ad35eb87aaf50c7fdbaf39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60739ca5ec1d417e709792231bc06b65af4f6d2e6cac2a883a725bc5acd553d901a2d7c2d9b7da2e1a592f2f66635e740d668070a05551ffcfbefff3b4102dba
|
7
|
+
data.tar.gz: 6e34ef248fb441a9dcbdd60e4a2566e9010b01900b7802c33653e77744c9d7c9d33a65f78649118beb20bdc1fc283775232a7b834d4d514a3c8ea797d7698ee4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
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
|
7
|
+
## [0.2.3] - 2021-06-23
|
8
|
+
|
9
|
+
- Fix extension_available?
|
10
|
+
|
3
11
|
## [0.2.2] - 2021-06-22
|
4
12
|
|
5
13
|
- Fix bug in Ruby 2.6 calling format wrong.
|
@@ -6,20 +6,36 @@ module ActiveRecord
|
|
6
6
|
# to executing, in order to reduce the amount of time the actual DDL takes to
|
7
7
|
# execute (and thus how long it needs the lock)
|
8
8
|
module PessimisticMigrations
|
9
|
-
#
|
9
|
+
# adds a temporary check constraint to reduce locking when changing to NOT NULL, and we're not in a transaction
|
10
10
|
def change_column_null(table, column, nullness, default = nil)
|
11
|
-
# no point in
|
11
|
+
# no point in doing extra work to avoid locking if we're already in a transaction
|
12
12
|
return super if nullness != false || open_transactions.positive?
|
13
|
+
return if columns(table).find { |c| c.name == column.to_s }&.null == false
|
14
|
+
|
15
|
+
temp_constraint_name = "chk_rails_#{table}_#{column}_not_null"
|
16
|
+
scope = quoted_scope(temp_constraint_name)
|
17
|
+
# check for temp constraint
|
18
|
+
valid = select_value(<<~SQL, "SCHEMA")
|
19
|
+
SELECT convalidated FROM pg_constraint INNER JOIN pg_namespace ON pg_namespace.oid=connamespace WHERE conname=#{scope[:name]} AND nspname=#{scope[:schema]}
|
20
|
+
SQL
|
21
|
+
if valid.nil?
|
22
|
+
add_check_constraint(table,
|
23
|
+
"#{quote_column_name(column)} IS NOT NULL",
|
24
|
+
name: temp_constraint_name,
|
25
|
+
validate: false)
|
26
|
+
end
|
27
|
+
begin
|
28
|
+
validate_constraint(table, temp_constraint_name)
|
29
|
+
rescue ActiveRecord::StatementInvalid => e
|
30
|
+
raise ActiveRecord::NotNullViolation.new(sql: e.sql, binds: e.binds) if e.cause.is_a?(PG::CheckViolation)
|
31
|
+
|
32
|
+
raise
|
33
|
+
end
|
13
34
|
|
14
35
|
transaction do
|
15
|
-
|
16
|
-
|
17
|
-
execute("SET LOCAL enable_indexscan=off")
|
18
|
-
execute("SET LOCAL enable_bitmapscan=off")
|
19
|
-
execute("SELECT COUNT(*) FROM #{quote_table_name(table)} WHERE #{quote_column_name(column)} IS NULL")
|
20
|
-
raise ActiveRecord::Rollback
|
36
|
+
super
|
37
|
+
remove_check_constraint(table, name: temp_constraint_name)
|
21
38
|
end
|
22
|
-
super
|
23
39
|
end
|
24
40
|
|
25
41
|
# several improvements:
|
@@ -47,6 +47,7 @@ module ActiveRecord
|
|
47
47
|
sql << " VERSION #{quote(version)}" if version
|
48
48
|
sql << " CASCADE" if cascade
|
49
49
|
execute(sql)
|
50
|
+
reload_type_map
|
50
51
|
@extensions&.delete(extension.to_s)
|
51
52
|
end
|
52
53
|
|
@@ -61,6 +62,7 @@ module ActiveRecord
|
|
61
62
|
sql << " TO #{quote(version)}" if version && version != true
|
62
63
|
sql << " SET SCHEMA #{schema}" if schema
|
63
64
|
execute(sql)
|
65
|
+
reload_type_map
|
64
66
|
@extensions&.delete(extension.to_s)
|
65
67
|
end
|
66
68
|
|
@@ -73,13 +75,14 @@ module ActiveRecord
|
|
73
75
|
sql << extensions.join(", ")
|
74
76
|
sql << " CASCADE" if cascade
|
75
77
|
execute(sql)
|
78
|
+
reload_type_map
|
76
79
|
@extensions&.except!(*extensions.map(&:to_s))
|
77
80
|
end
|
78
81
|
|
79
82
|
# check if a particular extension can be installed
|
80
83
|
def extension_available?(extension, version = nil)
|
81
84
|
sql = +"SELECT 1 FROM "
|
82
|
-
sql << version ? "
|
85
|
+
sql << (version ? "pg_available_extension_versions" : "pg_available_extensions")
|
83
86
|
sql << " WHERE name=#{quote(extension)}"
|
84
87
|
sql << " AND version=#{quote(version)}" if version
|
85
88
|
select_value(sql).to_i == 1
|
@@ -233,6 +236,56 @@ module ActiveRecord
|
|
233
236
|
select_value("SELECT pg_is_in_recovery()")
|
234
237
|
end
|
235
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
|
268
|
+
def with_statement_timeout(timeout)
|
269
|
+
timeout = 30 if timeout.nil? || timeout == true
|
270
|
+
|
271
|
+
transaction do
|
272
|
+
self.statement_timeout = timeout
|
273
|
+
yield
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
unless ::Rails.version >= "6.1"
|
278
|
+
def add_check_constraint(table_name, expression, name:, validate: true)
|
279
|
+
sql = +"ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(name)} CHECK (#{expression})" # rubocop:disable Layout/LineLength
|
280
|
+
sql << " NOT VALID" unless validate
|
281
|
+
execute(sql)
|
282
|
+
end
|
283
|
+
|
284
|
+
def remove_check_constraint(table_name, name:)
|
285
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(name)}")
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
236
289
|
private
|
237
290
|
|
238
291
|
if ::Rails.version < "6.1"
|
@@ -8,9 +8,13 @@ module ActiveRecord
|
|
8
8
|
class Railtie < Rails::Railtie
|
9
9
|
initializer "pg_extensions.extend_ar", after: "active_record.initialize_database" do
|
10
10
|
ActiveSupport.on_load(:active_record) do
|
11
|
+
require "active_record/pg_extensions/errors"
|
11
12
|
require "active_record/pg_extensions/postgresql_adapter"
|
13
|
+
require "active_record/pg_extensions/transaction"
|
12
14
|
|
13
|
-
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapter)
|
15
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapter)
|
16
|
+
::ActiveRecord::ConnectionAdapters::NullTransaction.prepend(NullTransaction)
|
17
|
+
::ActiveRecord::ConnectionAdapters::Transaction.prepend(Transaction)
|
14
18
|
# if they've already require 'all', then inject now
|
15
19
|
defined?(All) && All.inject
|
16
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
|
@@ -6,32 +6,61 @@ describe ActiveRecord::PGExtensions::PessimisticMigrations do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
describe "#change_column_null" do
|
9
|
+
# Rails 6.1 doesn't quote the constraint name when adding a check constraint??
|
10
|
+
def quote_constraint_name(name)
|
11
|
+
Rails.version >= "6.1" ? name : connection.quote_column_name(name)
|
12
|
+
end
|
13
|
+
|
9
14
|
it "does nothing extra when changing a column to nullable" do
|
10
15
|
connection.change_column_null(:table, :column, true)
|
11
16
|
expect(connection.executed_statements).to eq ['ALTER TABLE "table" ALTER COLUMN "column" DROP NOT NULL']
|
12
17
|
end
|
13
18
|
|
14
|
-
it "
|
19
|
+
it "does nothing if we're in a transaction" do
|
20
|
+
connection.transaction do
|
21
|
+
connection.change_column_null(:table, :column, true)
|
22
|
+
end
|
23
|
+
expect(connection.executed_statements).to eq ["BEGIN",
|
24
|
+
'ALTER TABLE "table" ALTER COLUMN "column" DROP NOT NULL',
|
25
|
+
"COMMIT"]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "skips entirely if the column is already NOT NULL" do
|
29
|
+
expect(connection).to receive(:columns).with(:table).and_return([double(name: "column", null: false)])
|
15
30
|
connection.change_column_null(:table, :column, false)
|
16
|
-
expect(connection.executed_statements).to eq(
|
17
|
-
["BEGIN",
|
18
|
-
"SET LOCAL enable_indexscan=off",
|
19
|
-
"SET LOCAL enable_bitmapscan=off",
|
20
|
-
'SELECT COUNT(*) FROM "table" WHERE "column" IS NULL',
|
21
|
-
"ROLLBACK",
|
22
|
-
'ALTER TABLE "table" ALTER COLUMN "column" SET NOT NULL']
|
23
|
-
)
|
31
|
+
expect(connection.executed_statements).to eq([])
|
24
32
|
end
|
25
33
|
|
26
|
-
it "
|
27
|
-
connection.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
it "adds and removes a check constraint" do
|
35
|
+
expect(connection).to receive(:columns).and_return([])
|
36
|
+
allow(connection).to receive(:check_constraint_for!).and_return(double(name: "chk_rails_table_column_not_null"))
|
37
|
+
connection.change_column_null(:table, :column, false)
|
38
|
+
|
39
|
+
expect(connection.executed_statements).to eq [
|
40
|
+
"SELECT convalidated FROM pg_constraint INNER JOIN pg_namespace ON pg_namespace.oid=connamespace WHERE conname='chk_rails_table_column_not_null' AND nspname=ANY (current_schemas(false))\n", # rubocop:disable Layout/LineLength
|
41
|
+
%{ALTER TABLE "table" ADD CONSTRAINT #{quote_constraint_name('chk_rails_table_column_not_null')} CHECK ("column" IS NOT NULL) NOT VALID}, # rubocop:disable Layout/LineLength
|
42
|
+
'ALTER TABLE "table" VALIDATE CONSTRAINT "chk_rails_table_column_not_null"',
|
43
|
+
"BEGIN",
|
44
|
+
'ALTER TABLE "table" ALTER COLUMN "column" SET NOT NULL',
|
45
|
+
'ALTER TABLE "table" DROP CONSTRAINT "chk_rails_table_column_not_null"',
|
46
|
+
"COMMIT"
|
47
|
+
]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "verifies an existing check constraint" do
|
51
|
+
expect(connection).to receive(:columns).and_return([])
|
52
|
+
allow(connection).to receive(:check_constraint_for!).and_return(double(name: "chk_rails_table_column_not_null"))
|
53
|
+
expect(connection).to receive(:select_value).and_return(false)
|
54
|
+
connection.change_column_null(:table, :column, false)
|
55
|
+
|
56
|
+
expect(connection.executed_statements).to eq [
|
57
|
+
# stubbed out <check constraint valid>
|
58
|
+
'ALTER TABLE "table" VALIDATE CONSTRAINT "chk_rails_table_column_not_null"',
|
59
|
+
"BEGIN",
|
60
|
+
'ALTER TABLE "table" ALTER COLUMN "column" SET NOT NULL',
|
61
|
+
'ALTER TABLE "table" DROP CONSTRAINT "chk_rails_table_column_not_null"',
|
62
|
+
"COMMIT"
|
63
|
+
]
|
35
64
|
end
|
36
65
|
end
|
37
66
|
|
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
|
4
|
+
before do
|
5
|
+
allow(connection).to receive(:reload_type_map)
|
6
|
+
end
|
7
|
+
|
4
8
|
describe "#set_constraints" do
|
5
9
|
around do |example|
|
6
10
|
connection.dont_execute(&example)
|
@@ -131,6 +135,20 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
|
|
131
135
|
it "does not allow dropping no extensions" do
|
132
136
|
expect { connection.drop_extension }.to raise_error(ArgumentError)
|
133
137
|
end
|
138
|
+
|
139
|
+
describe "#extension_available?" do
|
140
|
+
it "works with no version constraint" do
|
141
|
+
connection.extension_available?(:postgis)
|
142
|
+
expect(connection.executed_statements).to eq ["SELECT 1 FROM pg_available_extensions WHERE name='postgis'"]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "works with a version constraint" do
|
146
|
+
connection.extension_available?(:postgis, "2.0")
|
147
|
+
expect(connection.executed_statements).to eq(
|
148
|
+
["SELECT 1 FROM pg_available_extension_versions WHERE name='postgis' AND version='2.0'"]
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end
|
134
152
|
end
|
135
153
|
end
|
136
154
|
|
@@ -250,4 +268,190 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
|
|
250
268
|
end
|
251
269
|
end
|
252
270
|
end
|
271
|
+
|
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
|
+
|
281
|
+
it "stops long-running queries" do
|
282
|
+
expect do
|
283
|
+
connection.with_statement_timeout(0.01) do
|
284
|
+
connection.execute("SELECT pg_sleep(3)")
|
285
|
+
end
|
286
|
+
end.to raise_error(ActiveRecord::QueryCanceled)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "re-raises other errors" do
|
290
|
+
expect do
|
291
|
+
connection.with_statement_timeout(1) do
|
292
|
+
connection.execute("bad sql")
|
293
|
+
end
|
294
|
+
end.to(raise_error { |e| expect(e.cause).to be_a(PG::SyntaxError) })
|
295
|
+
end
|
296
|
+
|
297
|
+
context "without executing" do
|
298
|
+
around do |example|
|
299
|
+
connection.dont_execute(&example)
|
300
|
+
end
|
301
|
+
|
302
|
+
it "converts integer to ms" do
|
303
|
+
connection.with_statement_timeout(30) { nil }
|
304
|
+
expect(connection.executed_statements).to eq(
|
305
|
+
[
|
306
|
+
"BEGIN",
|
307
|
+
"SET LOCAL statement_timeout TO '30000ms'",
|
308
|
+
"COMMIT"
|
309
|
+
]
|
310
|
+
)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "converts float to ms" do
|
314
|
+
connection.with_statement_timeout(5.5) { nil }
|
315
|
+
expect(connection.executed_statements).to eq(
|
316
|
+
[
|
317
|
+
"BEGIN",
|
318
|
+
"SET LOCAL statement_timeout TO '5500ms'",
|
319
|
+
"COMMIT"
|
320
|
+
]
|
321
|
+
)
|
322
|
+
end
|
323
|
+
|
324
|
+
it "converts ActiveSupport::Duration to ms" do
|
325
|
+
connection.with_statement_timeout(5.seconds) { nil }
|
326
|
+
expect(connection.executed_statements).to eq(
|
327
|
+
[
|
328
|
+
"BEGIN",
|
329
|
+
"SET LOCAL statement_timeout TO '5000ms'",
|
330
|
+
"COMMIT"
|
331
|
+
]
|
332
|
+
)
|
333
|
+
end
|
334
|
+
|
335
|
+
it "allows true" do
|
336
|
+
connection.with_statement_timeout(true) { nil }
|
337
|
+
expect(connection.executed_statements).to eq(
|
338
|
+
[
|
339
|
+
"BEGIN",
|
340
|
+
"SET LOCAL statement_timeout TO '30000ms'",
|
341
|
+
"COMMIT"
|
342
|
+
]
|
343
|
+
)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
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
|
+
|
423
|
+
unless Rails.version >= "6.1"
|
424
|
+
describe "#add_check_constraint" do
|
425
|
+
around do |example|
|
426
|
+
connection.dont_execute(&example)
|
427
|
+
end
|
428
|
+
|
429
|
+
it "works" do
|
430
|
+
connection.add_check_constraint(:table, "column IS NOT NULL", name: :my_constraint)
|
431
|
+
expect(connection.executed_statements).to eq(
|
432
|
+
['ALTER TABLE "table" ADD CONSTRAINT "my_constraint" CHECK (column IS NOT NULL)']
|
433
|
+
)
|
434
|
+
end
|
435
|
+
|
436
|
+
it "defers validation" do
|
437
|
+
connection.add_check_constraint(:table, "column IS NOT NULL", name: :my_constraint, validate: false)
|
438
|
+
expect(connection.executed_statements).to eq(
|
439
|
+
['ALTER TABLE "table" ADD CONSTRAINT "my_constraint" CHECK (column IS NOT NULL) NOT VALID']
|
440
|
+
)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
describe "#remove_check_constraint" do
|
445
|
+
around do |example|
|
446
|
+
connection.dont_execute(&example)
|
447
|
+
end
|
448
|
+
|
449
|
+
it "works" do
|
450
|
+
connection.remove_check_constraint(:table, name: :my_constraint)
|
451
|
+
expect(connection.executed_statements).to eq(
|
452
|
+
['ALTER TABLE "table" DROP CONSTRAINT "my_constraint"']
|
453
|
+
)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
253
457
|
end
|
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.
|
4
|
+
version: 0.4.1
|
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-
|
11
|
+
date: 2021-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -175,10 +175,12 @@ files:
|
|
175
175
|
- config/database.yml
|
176
176
|
- lib/active_record/pg_extensions.rb
|
177
177
|
- lib/active_record/pg_extensions/all.rb
|
178
|
+
- lib/active_record/pg_extensions/errors.rb
|
178
179
|
- lib/active_record/pg_extensions/extension.rb
|
179
180
|
- lib/active_record/pg_extensions/pessimistic_migrations.rb
|
180
181
|
- lib/active_record/pg_extensions/postgresql_adapter.rb
|
181
182
|
- lib/active_record/pg_extensions/railtie.rb
|
183
|
+
- lib/active_record/pg_extensions/transaction.rb
|
182
184
|
- lib/active_record/pg_extensions/version.rb
|
183
185
|
- lib/activerecord-pg-extensions.rb
|
184
186
|
- spec/pessimistic_migrations_spec.rb
|
@@ -204,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
206
|
- !ruby/object:Gem::Version
|
205
207
|
version: '0'
|
206
208
|
requirements: []
|
207
|
-
rubygems_version: 3.
|
209
|
+
rubygems_version: 3.2.24
|
208
210
|
signing_key:
|
209
211
|
specification_version: 4
|
210
212
|
summary: Several extensions to ActiveRecord's PostgreSQLAdapter.
|