activerecord-pg-extensions 0.2.1 → 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 +4 -4
- data/CHANGELOG.md +12 -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 +55 -2
- 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 +49 -20
- data/spec/postgresql_adapter_spec.rb +205 -1
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3494201ce01b5c6d5104ae9ddb7f993310223ecc94bd902b2484ce6b6a4e525b
|
4
|
+
data.tar.gz: acd5b4c842cbfc275b2a86f009c048e7916709803f3d72c9ed40f42095c47a7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91625c727722fb91aa1860960a3f9c5ba7ba4dcab0a0cf459e6b8301a7fa734ff91882f5ae31ac4cb492218eaf17205ed97e54236cf8760d55b6c33b4cd3f7dc
|
7
|
+
data.tar.gz: fbe3e4a073f7f646438b07f1226e5c654a744c3efb421ea38b9c7b7adc5cf9e173fbab479780f765cc41f3c9144294d1ac16fe7f140e68bcdf44128a0bcafd68
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
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
|
+
|
11
|
+
## [0.2.2] - 2021-06-22
|
12
|
+
|
13
|
+
- Fix bug in Ruby 2.6 calling format wrong.
|
14
|
+
|
3
15
|
## [0.2.1] - 2021-06-22
|
4
16
|
|
5
17
|
- Ensure numeric is in the PG type map for Rails 6.0. So that lsn_diff will
|
@@ -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"
|
@@ -259,7 +312,7 @@ module ActiveRecord
|
|
259
312
|
end
|
260
313
|
|
261
314
|
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
262
|
-
query = <<~SQL
|
315
|
+
query = format(<<~SQL, *known_coder_types.join(", "))
|
263
316
|
SELECT t.oid, t.typname
|
264
317
|
FROM pg_type as t
|
265
318
|
WHERE t.typname IN (%s)
|
@@ -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.
|
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}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
|
@@ -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
|
|
@@ -92,8 +121,8 @@ describe ActiveRecord::PGExtensions::PessimisticMigrations do
|
|
92
121
|
expect(connection).to receive(:remove_index).with(:users, name: "index_users_on_name", algorithm: :concurrently)
|
93
122
|
|
94
123
|
connection.add_index :users, :name, algorithm: :concurrently
|
95
|
-
expect(connection.executed_statements).to
|
96
|
-
[
|
124
|
+
expect(connection.executed_statements).to match(
|
125
|
+
[match(/\ACREATE +INDEX CONCURRENTLY "index_users_on_name" ON "users" +\("name"\)\z/)]
|
97
126
|
)
|
98
127
|
end
|
99
128
|
|
@@ -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
|
|
@@ -245,9 +263,195 @@ describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
|
|
245
263
|
end
|
246
264
|
|
247
265
|
describe "#select_value" do
|
248
|
-
it "
|
266
|
+
it "casts numeric types" do
|
249
267
|
expect(connection.select_value("SELECT factorial(2)")).to eq 2
|
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 '30s'",
|
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 '5.5s'",
|
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 '5s'",
|
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 '30s'",
|
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 '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
|
+
|
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.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-
|
11
|
+
date: 2021-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -50,6 +50,20 @@ dependencies:
|
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: '6.2'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: appraisal
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '2.4'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.4'
|
53
67
|
- !ruby/object:Gem::Dependency
|
54
68
|
name: byebug
|
55
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,10 +175,12 @@ files:
|
|
161
175
|
- config/database.yml
|
162
176
|
- lib/active_record/pg_extensions.rb
|
163
177
|
- lib/active_record/pg_extensions/all.rb
|
178
|
+
- lib/active_record/pg_extensions/errors.rb
|
164
179
|
- lib/active_record/pg_extensions/extension.rb
|
165
180
|
- lib/active_record/pg_extensions/pessimistic_migrations.rb
|
166
181
|
- lib/active_record/pg_extensions/postgresql_adapter.rb
|
167
182
|
- lib/active_record/pg_extensions/railtie.rb
|
183
|
+
- lib/active_record/pg_extensions/transaction.rb
|
168
184
|
- lib/active_record/pg_extensions/version.rb
|
169
185
|
- lib/activerecord-pg-extensions.rb
|
170
186
|
- spec/pessimistic_migrations_spec.rb
|
@@ -190,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
190
206
|
- !ruby/object:Gem::Version
|
191
207
|
version: '0'
|
192
208
|
requirements: []
|
193
|
-
rubygems_version: 3.2.
|
209
|
+
rubygems_version: 3.2.24
|
194
210
|
signing_key:
|
195
211
|
specification_version: 4
|
196
212
|
summary: Several extensions to ActiveRecord's PostgreSQLAdapter.
|