activerecord-pg-extensions 0.4.4 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45b4e3d0cf437ce75246b882697214a96cf89e44b2b28420b5dd215f243f5351
4
- data.tar.gz: c49b15be7227aafe9de557cf78ef4d4938a842b72d28838f2c1bef59d49f1a7f
3
+ metadata.gz: 2f00b73b6ca45b6816eb823afa1cb16213baf9a9671d02c5633c4cf07fa92026
4
+ data.tar.gz: 84f12098d7d3d638671cdbcf32e9e1f4e56ece8b64cc799f167b2eff336cc7dd
5
5
  SHA512:
6
- metadata.gz: 1123584bcfb35e545245c3274e3e1d485ff9080cf46fdcf1788eddbcc7477dcbdc4a3e66c7d3742d3f6f9b701599454b81e994bd8c228eeb77560698c0b2369e
7
- data.tar.gz: 5fda99a4141dee1332a889f7123581ceb6ca5e321e064bff89e5015c63d50d73c1e0cb1c0f775ca1d67d29f4d04ff9a7bc7d98c02ea482efabfd8f28e4cb7c7a
6
+ metadata.gz: 414294a360874650bedada98a2ccb4fb73fe8f2eb85d2c020387280dd84739af6807c703c016f4c7027c030ffb2f57cc4a1e493104a08ec79b599993b36f56c1
7
+ data.tar.gz: 8333b90a0265330c6f9f99d8826839003086816726f041e685916254de9cecd89db4ee2b9815975080b40cdd9be784ddd24d1f96322a8bba12c6971c2f91f3a6
@@ -27,10 +27,8 @@ module ActiveRecord
27
27
  end
28
28
  begin
29
29
  validate_constraint(table, temp_constraint_name)
30
- rescue ActiveRecord::StatementInvalid => e
31
- raise ActiveRecord::NotNullViolation.new(sql: e.sql, binds: e.binds) if e.cause.is_a?(PG::CheckViolation)
32
-
33
- raise
30
+ rescue PG::CheckViolation => e
31
+ raise ActiveRecord::NotNullViolation.new(sql: e.sql, binds: e.binds)
34
32
  end
35
33
 
36
34
  transaction do
@@ -66,7 +64,7 @@ module ActiveRecord
66
64
  # will automatically remove a NOT VALID index before trying to add
67
65
  def add_index(table_name, column_name, **options)
68
66
  # catch a concurrent index add that fails because it already exists, and is invalid
69
- if options[:algorithm] == :concurrently || options[:if_not_exists]
67
+ if options[:algorithm] == :concurrently && options[:if_not_exists]
70
68
  column_names = index_column_names(column_name)
71
69
  index_name = options[:name].to_s if options.key?(:name)
72
70
  index_name ||= index_name(table_name, column_names)
@@ -84,16 +82,26 @@ module ActiveRecord
84
82
  AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{index[:schema]} )
85
83
  LIMIT 1
86
84
  SQL
87
- if valid == false && options[:algorithm] == :concurrently
88
- remove_index(table_name, name: index_name,
89
- algorithm: :concurrently)
90
- end
91
- return if options[:if_not_exists] && valid == true
85
+ return if valid == true
86
+
87
+ remove_index(table_name, name: index_name, algorithm: :concurrently) if valid == false
92
88
  end
93
- # Rails.version: can stop doing this in Rails 6.2, when it's natively supported
94
- options.delete(:if_not_exists)
95
89
  super
96
90
  end
91
+
92
+ def add_check_constraint(table_name, expression, if_not_exists: false, **options)
93
+ return if if_not_exists && check_constraint_for(table_name, expression, **options)
94
+
95
+ super
96
+ end
97
+
98
+ if ActiveRecord.version < Gem::Version.new("7.1")
99
+ def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
100
+ return if if_exists && !check_constraint_for(table_name, expression, **options)
101
+
102
+ super
103
+ end
104
+ end
97
105
  end
98
106
  end
99
107
  end
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  # set constraint check timing for the current transaction
10
10
  # see https://www.postgresql.org/docs/current/sql-set-constraints.html
11
11
  def set_constraints(deferred, *constraints)
12
- raise ArgumentError, "deferred must be :deferred or :immediate" unless %w[deferred
13
- immediate].include?(deferred.to_s)
12
+ raise ArgumentError, "deferred must be :deferred or :immediate" unless %i[deferred
13
+ immediate].include?(deferred)
14
14
 
15
15
  constraints = constraints.map { |c| quote_table_name(c) }.join(", ")
16
16
  constraints = "ALL" if constraints.empty?
@@ -160,7 +160,7 @@ module ActiveRecord
160
160
  raise ArgumentError, "columns may only be specified if a analyze is specified" unless analyze
161
161
 
162
162
  table.map do |table_name, columns|
163
- "#{quote_table_name(table_name)} (#{Array.wrap(columns).map { |c| quote_column_name(c) }.join(', ')})"
163
+ "#{quote_table_name(table_name)} (#{Array.wrap(columns).map { |c| quote_column_name(c) }.join(", ")})"
164
164
  end.join(", ")
165
165
  else
166
166
  quote_table_name(table)
@@ -182,35 +182,35 @@ module ActiveRecord
182
182
  def current_wal_lsn
183
183
  return nil unless wal?
184
184
 
185
- select_value("SELECT #{pre_pg10_wal_function_name('pg_current_wal_lsn')}()")
185
+ select_value("SELECT #{pre_pg10_wal_function_name("pg_current_wal_lsn")}()")
186
186
  end
187
187
 
188
188
  # see https://www.postgresql.org/docs/current/functions-admin.html#id-1.5.8.33.5.5.2.2.2.1.1.1
189
189
  def current_wal_flush_lsn
190
190
  return nil unless wal?
191
191
 
192
- select_value("SELECT #{pre_pg10_wal_function_name('pg_current_wal_flush_lsn')}()")
192
+ select_value("SELECT #{pre_pg10_wal_function_name("pg_current_wal_flush_lsn")}()")
193
193
  end
194
194
 
195
195
  # see https://www.postgresql.org/docs/current/functions-admin.html#id-1.5.8.33.5.5.2.2.3.1.1.1
196
196
  def current_wal_insert_lsn
197
197
  return nil unless wal?
198
198
 
199
- select_value("SELECT #{pre_pg10_wal_function_name('pg_current_wal_insert_lsn')}()")
199
+ select_value("SELECT #{pre_pg10_wal_function_name("pg_current_wal_insert_lsn")}()")
200
200
  end
201
201
 
202
202
  # https://www.postgresql.org/docs/current/functions-admin.html#id-1.5.8.33.6.3.2.2.2.1.1.1
203
203
  def last_wal_receive_lsn
204
204
  return nil unless wal?
205
205
 
206
- select_value("SELECT #{pre_pg10_wal_function_name('pg_last_wal_receive_lsn')}()")
206
+ select_value("SELECT #{pre_pg10_wal_function_name("pg_last_wal_receive_lsn")}()")
207
207
  end
208
208
 
209
209
  # see https://www.postgresql.org/docs/current/functions-admin.html#id-1.5.8.33.6.3.2.2.3.1.1.1
210
210
  def last_wal_replay_lsn
211
211
  return nil unless wal?
212
212
 
213
- select_value("SELECT #{pre_pg10_wal_function_name('pg_last_wal_replay_lsn')}()")
213
+ select_value("SELECT #{pre_pg10_wal_function_name("pg_last_wal_replay_lsn")}()")
214
214
  end
215
215
 
216
216
  # see https://www.postgresql.org/docs/current/functions-admin.html#id-1.5.8.33.5.5.2.2.4.1.1.1
@@ -229,7 +229,7 @@ module ActiveRecord
229
229
  end
230
230
  end
231
231
 
232
- select_value("SELECT #{pre_pg10_wal_function_name('pg_wal_lsn_diff')}(#{lsns[0]}, #{lsns[1]})")
232
+ select_value("SELECT #{pre_pg10_wal_function_name("pg_wal_lsn_diff")}(#{lsns[0]}, #{lsns[1]})")
233
233
  end
234
234
 
235
235
  def in_recovery?
@@ -238,7 +238,7 @@ module ActiveRecord
238
238
 
239
239
  def set(configuration_parameter, value, local: false)
240
240
  value = value.nil? ? "DEFAULT" : quote(value)
241
- execute("SET#{' LOCAL' if local} #{configuration_parameter} TO #{value}")
241
+ execute("SET#{" LOCAL" if local} #{configuration_parameter} TO #{value}")
242
242
  end
243
243
 
244
244
  def reset(configuration_parameter)
@@ -274,65 +274,8 @@ module ActiveRecord
274
274
  end
275
275
  end
276
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
-
289
277
  private
290
278
 
291
- if ::Rails.version < "6.1"
292
- # significant change: add PG::TextDecoder::Numeric
293
- def add_pg_decoders
294
- @default_timezone = nil
295
- @timestamp_decoder = nil
296
-
297
- coders_by_name = {
298
- "int2" => PG::TextDecoder::Integer,
299
- "int4" => PG::TextDecoder::Integer,
300
- "int8" => PG::TextDecoder::Integer,
301
- "oid" => PG::TextDecoder::Integer,
302
- "float4" => PG::TextDecoder::Float,
303
- "float8" => PG::TextDecoder::Float,
304
- "bool" => PG::TextDecoder::Boolean,
305
- "numeric" => PG::TextDecoder::Numeric
306
- }
307
-
308
- if defined?(PG::TextDecoder::TimestampUtc)
309
- # Use native PG encoders available since pg-1.1
310
- coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
311
- coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
312
- end
313
-
314
- known_coder_types = coders_by_name.keys.map { |n| quote(n) }
315
- query = format(<<~SQL, *known_coder_types.join(", "))
316
- SELECT t.oid, t.typname
317
- FROM pg_type as t
318
- WHERE t.typname IN (%s)
319
- SQL
320
- coders = execute_and_clear(query, "SCHEMA", []) do |result|
321
- result
322
- .map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
323
- .compact
324
- end
325
-
326
- map = PG::TypeMapByOid.new
327
- coders.each { |coder| map.add_coder(coder) }
328
- @connection.type_map_for_results = map
329
-
330
- # extract timestamp decoder for use in update_typemap_for_default_timezone
331
- @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
332
- update_typemap_for_default_timezone
333
- end
334
- end
335
-
336
279
  def initialize_type_map(map = type_map)
337
280
  map.register_type "pg_lsn", ActiveRecord::ConnectionAdapters::PostgreSQL::OID::SpecializedString.new(:pg_lsn)
338
281
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module PGExtensions
5
- VERSION = "0.4.4"
5
+ VERSION = "0.5.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,55 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-pg-extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.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: 2022-03-18 00:00:00.000000000 Z
11
+ date: 2023-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '6.0'
20
- - - "<"
17
+ - - "~>"
21
18
  - !ruby/object:Gem::Version
22
- version: '7.1'
19
+ version: 7.0.0
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: '6.0'
30
- - - "<"
24
+ - - "~>"
31
25
  - !ruby/object:Gem::Version
32
- version: '7.1'
26
+ version: 7.0.0
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: railties
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '6.0'
40
- - - "<"
31
+ - - "~>"
41
32
  - !ruby/object:Gem::Version
42
- version: '7.1'
33
+ version: 7.0.0
43
34
  type: :runtime
44
35
  prerelease: false
45
36
  version_requirements: !ruby/object:Gem::Requirement
46
37
  requirements:
47
- - - ">="
48
- - !ruby/object:Gem::Version
49
- version: '6.0'
50
- - - "<"
38
+ - - "~>"
51
39
  - !ruby/object:Gem::Version
52
- version: '7.1'
40
+ version: 7.0.0
53
41
  - !ruby/object:Gem::Dependency
54
42
  name: appraisal
55
43
  requirement: !ruby/object:Gem::Requirement
@@ -121,19 +109,19 @@ dependencies:
121
109
  - !ruby/object:Gem::Version
122
110
  version: '3.0'
123
111
  - !ruby/object:Gem::Dependency
124
- name: rubocop
112
+ name: rubocop-inst
125
113
  requirement: !ruby/object:Gem::Requirement
126
114
  requirements:
127
115
  - - "~>"
128
116
  - !ruby/object:Gem::Version
129
- version: '1.7'
117
+ version: '1'
130
118
  type: :development
131
119
  prerelease: false
132
120
  version_requirements: !ruby/object:Gem::Requirement
133
121
  requirements:
134
122
  - - "~>"
135
123
  - !ruby/object:Gem::Version
136
- version: '1.7'
124
+ version: '1'
137
125
  - !ruby/object:Gem::Dependency
138
126
  name: rubocop-rake
139
127
  requirement: !ruby/object:Gem::Requirement
@@ -172,7 +160,6 @@ files:
172
160
  - CHANGELOG.md
173
161
  - LICENSE.txt
174
162
  - README.md
175
- - config/database.yml
176
163
  - lib/active_record/pg_extensions.rb
177
164
  - lib/active_record/pg_extensions/all.rb
178
165
  - lib/active_record/pg_extensions/errors.rb
@@ -183,9 +170,6 @@ files:
183
170
  - lib/active_record/pg_extensions/transaction.rb
184
171
  - lib/active_record/pg_extensions/version.rb
185
172
  - lib/activerecord-pg-extensions.rb
186
- - spec/pessimistic_migrations_spec.rb
187
- - spec/postgresql_adapter_spec.rb
188
- - spec/spec_helper.rb
189
173
  homepage: https://github.com/instructure/activerecord-pg-extensions
190
174
  licenses:
191
175
  - MIT
@@ -200,19 +184,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
200
184
  requirements:
201
185
  - - ">="
202
186
  - !ruby/object:Gem::Version
203
- version: 2.6.0
187
+ version: '2.7'
204
188
  required_rubygems_version: !ruby/object:Gem::Requirement
205
189
  requirements:
206
190
  - - ">="
207
191
  - !ruby/object:Gem::Version
208
192
  version: '0'
209
193
  requirements: []
210
- rubygems_version: 3.1.4
194
+ rubygems_version: 3.4.19
211
195
  signing_key:
212
196
  specification_version: 4
213
197
  summary: Several extensions to ActiveRecord's PostgreSQLAdapter.
214
- test_files:
215
- - spec/spec_helper.rb
216
- - spec/postgresql_adapter_spec.rb
217
- - spec/pessimistic_migrations_spec.rb
218
- - config/database.yml
198
+ test_files: []
data/config/database.yml DELETED
@@ -1,3 +0,0 @@
1
- test:
2
- adapter: postgresql
3
- database: arpgextensions
@@ -1,136 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe ActiveRecord::PGExtensions::PessimisticMigrations do
4
- around do |example|
5
- connection.dont_execute(&example)
6
- end
7
-
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
-
14
- it "does nothing extra when changing a column to nullable" do
15
- connection.change_column_null(:table, :column, true)
16
- expect(connection.executed_statements).to eq ['ALTER TABLE "table" ALTER COLUMN "column" DROP NOT NULL']
17
- end
18
-
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)])
30
- connection.change_column_null(:table, :column, false)
31
- expect(connection.executed_statements).to eq([])
32
- end
33
-
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
- ]
64
- end
65
- end
66
-
67
- describe "#add_foreign_key" do
68
- it "does nothing extra if a transaction is already active" do
69
- connection.transaction do
70
- connection.add_foreign_key :emails, :users, delay_validation: true
71
- end
72
- expect(connection.executed_statements).to match(
73
- ["BEGIN",
74
- match(/\AALTER TABLE "emails" ADD CONSTRAINT "fk_rails_[0-9a-f]+".+REFERENCES "users" \("id"\)\s*\z/m),
75
- "COMMIT"]
76
- )
77
- end
78
-
79
- it "delays validation" do
80
- connection.add_foreign_key :emails, :users, delay_validation: true
81
- expect(connection.executed_statements).to match(
82
- [/convalidated/,
83
- match(/\AALTER TABLE "emails" ADD CONSTRAINT "[a-z0-9_]+".+REFERENCES "users" \("id"\)\s+NOT VALID\z/m),
84
- match(/^ALTER TABLE "emails" VALIDATE CONSTRAINT "fk_rails_[0-9a-f]+"/)]
85
- )
86
- end
87
-
88
- it "only validates if the constraint already exists, and is not valid" do
89
- expect(connection).to receive(:select_value).with(/convalidated/, "SCHEMA").and_return(false)
90
- connection.add_foreign_key :emails, :users, delay_validation: true
91
- expect(connection.executed_statements).to match(
92
- [match(/^ALTER TABLE "emails" VALIDATE CONSTRAINT "fk_rails_[0-9a-f]+"/)]
93
- )
94
- end
95
-
96
- it "does nothing if constraint already exists" do
97
- expect(connection).to receive(:select_value).with(/convalidated/, "SCHEMA").and_return(true)
98
- connection.add_foreign_key :emails, :users, if_not_exists: true
99
- expect(connection.executed_statements).to eq []
100
- end
101
-
102
- it "still tries if delay_validation is true but if_not_exists is false and it already exists" do
103
- expect(connection).to receive(:select_value).with(/convalidated/, "SCHEMA").and_return(true)
104
- connection.add_foreign_key :emails, :users, delay_validation: true
105
- expect(connection.executed_statements).to match(
106
- [match(/\AALTER TABLE "emails" ADD CONSTRAINT "[a-z0-9_]+".+REFERENCES "users" \("id"\)\s+NOT VALID\z/m),
107
- match(/^ALTER TABLE "emails" VALIDATE CONSTRAINT "fk_rails_[0-9a-f]+"/)]
108
- )
109
- end
110
-
111
- it "does nothing if_not_exists is true and it is NOT VALID" do
112
- expect(connection).to receive(:select_value).with(/convalidated/, "SCHEMA").and_return(false)
113
- connection.add_foreign_key :emails, :users, if_not_exists: true
114
- expect(connection.executed_statements).to eq []
115
- end
116
- end
117
-
118
- describe "#add_index" do
119
- it "removes a NOT VALID index before re-adding" do
120
- expect(connection).to receive(:select_value).with(/indisvalid/, "SCHEMA").and_return(false)
121
- expect(connection).to receive(:remove_index).with(:users, name: "index_users_on_name", algorithm: :concurrently)
122
-
123
- connection.add_index :users, :name, algorithm: :concurrently
124
- expect(connection.executed_statements).to match(
125
- [match(/\ACREATE +INDEX CONCURRENTLY "index_users_on_name" ON "users" +\("name"\)\z/)]
126
- )
127
- end
128
-
129
- it "does nothing if the index already exists" do
130
- expect(connection).to receive(:select_value).with(/indisvalid/, "SCHEMA").and_return(true)
131
-
132
- connection.add_index :users, :name, if_not_exists: true
133
- expect(connection.executed_statements).to eq []
134
- end
135
- end
136
- end
@@ -1,457 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter do
4
- before do
5
- allow(connection).to receive(:reload_type_map)
6
- end
7
-
8
- describe "#set_constraints" do
9
- around do |example|
10
- connection.dont_execute(&example)
11
- end
12
-
13
- it "requires :deferred or :immediate" do
14
- expect { connection.set_constraints("garbage") }.to raise_error(ArgumentError)
15
- end
16
-
17
- it "defaults to all" do
18
- connection.set_constraints(:deferred)
19
- expect(connection.executed_statements).to eq ["SET CONSTRAINTS ALL DEFERRED"]
20
- end
21
-
22
- it "quotes constraints" do
23
- connection.set_constraints(:deferred, :my_fk)
24
- expect(connection.executed_statements).to eq ['SET CONSTRAINTS "my_fk" DEFERRED']
25
- end
26
-
27
- it "quotes multiple constraints" do
28
- connection.set_constraints(:deferred, :my_fk1, :my_fk2)
29
- expect(connection.executed_statements).to eq ['SET CONSTRAINTS "my_fk1", "my_fk2" DEFERRED']
30
- end
31
- end
32
-
33
- describe "#defer_constraints" do
34
- around do |example|
35
- connection.dont_execute(&example)
36
- end
37
-
38
- it "defers and resets" do
39
- block_called = false
40
- connection.defer_constraints do
41
- block_called = true
42
- end
43
- expect(block_called).to eq true
44
- expect(connection.executed_statements).to eq ["SET CONSTRAINTS ALL DEFERRED", "SET CONSTRAINTS ALL IMMEDIATE"]
45
- end
46
- end
47
-
48
- describe "#set_replica_identity" do
49
- around do |example|
50
- connection.dont_execute(&example)
51
- end
52
-
53
- it "resets identity" do
54
- connection.set_replica_identity(:my_table)
55
- expect(connection.executed_statements).to eq ['ALTER TABLE "my_table" REPLICA IDENTITY DEFAULT']
56
- end
57
-
58
- it "sets full identity" do
59
- connection.set_replica_identity(:my_table, :full)
60
- expect(connection.executed_statements).to eq ['ALTER TABLE "my_table" REPLICA IDENTITY FULL']
61
- end
62
-
63
- it "sets an index" do
64
- connection.set_replica_identity(:my_table, :my_index)
65
- expect(connection.executed_statements).to eq ['ALTER TABLE "my_table" REPLICA IDENTITY USING INDEX "my_index"']
66
- end
67
- end
68
-
69
- context "extensions" do
70
- it "creates and drops an extension" do
71
- connection.create_extension(:pg_trgm, schema: "public")
72
- expect(connection.executed_statements).to eq ["CREATE EXTENSION pg_trgm SCHEMA public"]
73
- expect(ext = connection.extension(:pg_trgm)).not_to be_nil
74
- expect(ext.schema).to eq "public"
75
- expect(ext.version).not_to be_nil
76
- expect(ext.name).to eq "pg_trgm"
77
- ensure
78
- connection.executed_statements.clear
79
- connection.drop_extension(:pg_trgm, if_exists: true)
80
- expect(connection.executed_statements).to eq ["DROP EXTENSION IF EXISTS pg_trgm"]
81
- expect(connection.extension(:pg_trgm)).to be_nil
82
- end
83
-
84
- it "doesn't try to recreate" do
85
- connection.create_extension(:pg_trgm, schema: "public")
86
- connection.create_extension(:pg_trgm, schema: "public", if_not_exists: true)
87
- expect(connection.executed_statements).to eq ["CREATE EXTENSION pg_trgm SCHEMA public",
88
- "CREATE EXTENSION IF NOT EXISTS pg_trgm SCHEMA public"]
89
- ensure
90
- connection.drop_extension(:pg_trgm, if_exists: true)
91
- end
92
-
93
- context "non-executing" do
94
- around do |example|
95
- connection.dont_execute(&example)
96
- end
97
-
98
- it "supports all options on create" do
99
- connection.create_extension(:my_extension, if_not_exists: true, schema: "public", version: "2.0", cascade: true)
100
- expect(connection.executed_statements).to eq(
101
- ["CREATE EXTENSION IF NOT EXISTS my_extension SCHEMA public VERSION '2.0' CASCADE"]
102
- )
103
- end
104
-
105
- it "supports all options on drop" do
106
- connection.drop_extension(:my_extension, if_exists: true, cascade: true)
107
- expect(connection.executed_statements).to eq ["DROP EXTENSION IF EXISTS my_extension CASCADE"]
108
- end
109
-
110
- it "can update an extensions" do
111
- connection.alter_extension(:my_extension, version: true)
112
- expect(connection.executed_statements).to eq ["ALTER EXTENSION my_extension UPDATE"]
113
- end
114
-
115
- it "can update to a specific version" do
116
- connection.alter_extension(:my_extension, version: "2.0")
117
- expect(connection.executed_statements).to eq ["ALTER EXTENSION my_extension UPDATE TO '2.0'"]
118
- end
119
-
120
- it "can change schemas" do
121
- connection.alter_extension(:my_extension, schema: "my_app")
122
- expect(connection.executed_statements).to eq ["ALTER EXTENSION my_extension SET SCHEMA my_app"]
123
- end
124
-
125
- it "cannot change schema and update" do
126
- expect { connection.alter_extension(:my_extension, schema: "my_app", version: "2.0") }
127
- .to raise_error(ArgumentError)
128
- end
129
-
130
- it "can drop multiple extensions" do
131
- connection.drop_extension(:my_extension1, :my_extension2)
132
- expect(connection.executed_statements).to eq ["DROP EXTENSION my_extension1, my_extension2"]
133
- end
134
-
135
- it "does not allow dropping no extensions" do
136
- expect { connection.drop_extension }.to raise_error(ArgumentError)
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
152
- end
153
- end
154
-
155
- describe "#add_schema_to_search_path" do
156
- around do |example|
157
- original_search_path = connection.schema_search_path
158
- connection.schema_search_path = "public"
159
- example.call
160
- ensure
161
- connection.schema_search_path = original_search_path
162
- end
163
-
164
- it "adds a schema to search path" do
165
- connection.add_schema_to_search_path("postgis") do
166
- expect(connection.schema_search_path).to eq "public,postgis"
167
- end
168
- expect(connection.schema_search_path).to eq "public"
169
- end
170
-
171
- it "doesn't duplicate an existing schema" do
172
- connection.add_schema_to_search_path("public") do
173
- expect(connection.schema_search_path).to eq "public"
174
- end
175
- expect(connection.schema_search_path).to eq "public"
176
- end
177
-
178
- it "is cleaned up properly when the transaction rolls back manually" do
179
- expect do
180
- connection.add_schema_to_search_path("postgis") do
181
- raise ActiveRecord::Rollback
182
- end
183
- end.to raise_error(ActiveRecord::Rollback)
184
- expect(connection.schema_search_path).to eq "public"
185
- end
186
-
187
- it "is cleaned up properly when the transaction rolls back" do
188
- expect do
189
- connection.add_schema_to_search_path("postgis") do
190
- connection.execute("gibberish")
191
- end
192
- end.to raise_error(ActiveRecord::StatementInvalid)
193
- expect(connection.schema_search_path).to eq "public"
194
- end
195
- end
196
-
197
- describe "#vacuum" do
198
- it "does a straight vacuum of everything" do
199
- connection.vacuum
200
- expect(connection.executed_statements).to eq ["VACUUM"]
201
- end
202
-
203
- it "supports several options" do
204
- connection.vacuum(analyze: true, verbose: true)
205
- expect(connection.executed_statements).to eq ["VACUUM VERBOSE ANALYZE"]
206
- end
207
-
208
- it "validates parallel option is an integer" do
209
- expect { connection.vacuum(parallel: :garbage) }.to raise_error(ArgumentError)
210
- end
211
-
212
- it "validates parallel option is postive" do
213
- expect { connection.vacuum(parallel: -1) }.to raise_error(ArgumentError)
214
- end
215
-
216
- context "non-executing" do
217
- around do |example|
218
- connection.dont_execute(&example)
219
- end
220
-
221
- it "vacuums a table" do
222
- connection.vacuum(:my_table)
223
- expect(connection.executed_statements).to eq ['VACUUM "my_table"']
224
- end
225
-
226
- it "vacuums multiple tables" do
227
- connection.vacuum(:table1, :table2)
228
- expect(connection.executed_statements).to eq ['VACUUM "table1", "table2"']
229
- end
230
-
231
- it "requires analyze with specific columns" do
232
- expect { connection.vacuum({ my_table: :column1 }) }.to raise_error(ArgumentError)
233
- end
234
-
235
- it "analyzes a specific column" do
236
- connection.vacuum({ my_table: :column }, analyze: true)
237
- expect(connection.executed_statements).to eq ['VACUUM ANALYZE "my_table" ("column")']
238
- end
239
-
240
- it "analyzes multiples columns" do
241
- connection.vacuum({ my_table: %i[column1 column2] }, analyze: true)
242
- expect(connection.executed_statements).to eq ['VACUUM ANALYZE "my_table" ("column1", "column2")']
243
- end
244
-
245
- it "analyzes a mixture of tables and columns" do
246
- connection.vacuum(:table1, { my_table: %i[column1 column2] }, analyze: true)
247
- expect(connection.executed_statements).to eq ['VACUUM ANALYZE "table1", "my_table" ("column1", "column2")']
248
- end
249
- end
250
-
251
- describe "#wal_lsn_diff" do
252
- skip unless connection.wal?
253
-
254
- it "executes" do
255
- expect(connection.wal_lsn_diff(:current, :current)).to eq 0
256
- end
257
- end
258
-
259
- describe "#in_recovery?" do
260
- it "works" do
261
- expect(connection.in_recovery?).to eq false
262
- end
263
- end
264
-
265
- describe "#select_value" do
266
- it "casts numeric types" do
267
- expect(connection.select_value("SELECT factorial(2)")).to eq 2
268
- end
269
- end
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
457
- end
data/spec/spec_helper.rb DELETED
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "activerecord-pg-extensions"
4
- require "byebug"
5
- require "active_record/railtie"
6
- require "active_record/pg_extensions/all"
7
-
8
- ActiveRecord::Base # rubocop:disable Lint/Void
9
- Rails.env = "test"
10
-
11
- class Application < Rails::Application
12
- config.eager_load = false
13
- end
14
- Application.initialize!
15
-
16
- ActiveRecord::Tasks::DatabaseTasks.create_all
17
-
18
- module StatementCaptureConnection
19
- def dont_execute
20
- @dont_execute = true
21
- yield
22
- ensure
23
- @dont_execute = false
24
- end
25
-
26
- def executed_statements
27
- @executed_statements ||= []
28
- end
29
-
30
- %w[execute exec_no_cache exec_cache].each do |method|
31
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
32
- def #{method}(statement, *#{Rails.version >= '7.0' ? ', **kwargs' : ''})
33
- materialize_transactions # this still needs to get called, even if we skip actually executing
34
- executed_statements << statement
35
- return empty_pg_result if @dont_execute
36
-
37
- super
38
- end
39
- RUBY
40
- end
41
-
42
- # we can't actually generate a dummy one of these, so we just query the db with something
43
- # that won't return anything
44
- def empty_pg_result
45
- @connection.async_exec("SELECT 0 WHERE FALSE")
46
- end
47
- end
48
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(StatementCaptureConnection)
49
-
50
- RSpec.configure do |config|
51
- config.expect_with :rspec do |c|
52
- c.syntax = :expect
53
- end
54
-
55
- def connection
56
- ActiveRecord::Base.connection
57
- end
58
-
59
- config.before do
60
- connection.executed_statements.clear
61
- end
62
- end