pg_ha_migrations 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e104b78dae3bcbec72dc3f81c403d4c70935e43f67ef43d5363fe39de4e4139f
4
- data.tar.gz: fe17e6f106fff9507d9ad62e2a6953d33aad1335005bd43c4b6ed2a7d6d1271d
3
+ metadata.gz: b7db2dcf4e9c5703c49b53e785ec06df44f3dc94fa190c81fe30e2426ed588d9
4
+ data.tar.gz: 6b763dbe8f8126bf26c2682ab69e50ea54df505ba099bf0f981e11a5831dee11
5
5
  SHA512:
6
- metadata.gz: a2b274232130fec7807eced8199dc19fa6c0e9eda29990ffe86815e942d1967ed5105c420bad89c7811a3223fcbedf372bdb6bccb292d27f274674e5e1df879d
7
- data.tar.gz: 5bbc97af919639056e152186dc5eebfd90ee19fe445732d064774cdf132dd303bed41afae80ab9f39d88a392d5c1edede948db87189d4f59c87f0fe9a2861071
6
+ metadata.gz: a92e9ba724c5d8faf39b0314430ea4848e2081926c1f02902e4bffb947cde09ccc2de1c4db74b5cb54ee3fb6b8db3072b88dab2ed10432965fbd612ec3d0c0c2
7
+ data.tar.gz: 5bf9858b2f1dc84936dae0128f4ddc43f7ae4961ae2564c5b23afdb9f9f8dbad1b9abe527aabe48bd0afca74e6f8d3a457a412f89ab54ef210dcceeaf5ba60ef
data/README.md CHANGED
@@ -304,6 +304,25 @@ Safely validate (without acquiring an exclusive lock) existing rows for a newly
304
304
  safe_validate_check_constraint :table, name: :constraint_table_on_column_like_example
305
305
  ```
306
306
 
307
+ #### safe\_add\_foreign\_key
308
+
309
+ Safely add a foreign key constraint. The constraint is first added as `NOT VALID` (to avoid blocking writes while the table is scanned) and then validated in a separate step that allows concurrent reads and writes.
310
+
311
+ ```ruby
312
+ safe_add_foreign_key :orders, :customers
313
+ ```
314
+
315
+ With custom options:
316
+
317
+ ```ruby
318
+ safe_add_foreign_key :orders, :customers,
319
+ column: :buyer_id,
320
+ name: :fk_orders_buyer,
321
+ on_delete: :cascade
322
+ ```
323
+
324
+ > **Note:** This method runs multiple DDL statements non-transactionally. The validation step performs a full table scan to verify existing rows satisfy the constraint. While no exclusive lock is held during validation, the scan may take a long time on large tables.
325
+
307
326
  #### safe\_rename\_constraint
308
327
 
309
328
  Safely rename any (not just `CHECK`) constraint.
@@ -270,7 +270,21 @@ module PgHaMigrations::SafeStatements
270
270
  unless ActiveRecord::Base.connection.postgresql_version >= 9_06_00
271
271
  raise PgHaMigrations::InvalidMigrationError, "Removing an index concurrently is not supported on Postgres 9.1 databases"
272
272
  end
273
- index_size = select_value("SELECT pg_size_pretty(pg_relation_size('#{options[:name]}'))")
273
+
274
+ index_exists = select_value(
275
+ "SELECT EXISTS(SELECT 1 FROM pg_class WHERE relname = #{connection.quote(options[:name].to_s)} AND relkind = 'i')"
276
+ )
277
+
278
+ unless index_exists
279
+ if options[:if_exists]
280
+ say "Index #{options[:name]} does not exist, skipping removal"
281
+ return
282
+ else
283
+ raise ArgumentError, "Index #{options[:name]} does not exist"
284
+ end
285
+ end
286
+
287
+ index_size = select_value("SELECT pg_size_pretty(pg_relation_size(#{connection.quote(options[:name].to_s)}))")
274
288
  say "Preparing to drop index #{options[:name]} which is #{index_size} on disk..."
275
289
  unsafe_remove_index(table, **options.merge(:algorithm => :concurrently))
276
290
  end
@@ -383,6 +397,17 @@ module PgHaMigrations::SafeStatements
383
397
  end
384
398
  end
385
399
 
400
+ def safe_add_foreign_key(from_table, to_table, **options)
401
+ # Rails convention for determining the foreign key column
402
+ column = options.fetch(:column) { "#{to_table.to_s.singularize}_id" }
403
+
404
+ # Rails convention for generating FK name: fk_rails_<10 char hash of column>
405
+ fk_name = options.fetch(:name) { "fk_rails_#{OpenSSL::Digest::SHA256.hexdigest(column.to_s).first(10)}" }
406
+
407
+ unsafe_add_foreign_key(from_table, to_table, validate: false, **options.merge(name: fk_name))
408
+ safe_validate_check_constraint(from_table, name: fk_name)
409
+ end
410
+
386
411
  def safe_rename_constraint(table, from:, to:)
387
412
  raise ArgumentError, "Expected <from> to be present" unless from.present?
388
413
  raise ArgumentError, "Expected <to> to be present" unless to.present?
@@ -683,13 +708,15 @@ module PgHaMigrations::SafeStatements
683
708
 
684
709
  def adjust_lock_timeout(timeout_seconds = PgHaMigrations::LOCK_TIMEOUT_SECONDS, &block)
685
710
  _check_postgres_adapter!
686
- original_timeout = ActiveRecord::Base.value_from_sql("SHOW lock_timeout").sub(/s\Z/, '').to_i * 1000
711
+ original_timeout_ms = ActiveRecord::Base.value_from_sql(
712
+ "SELECT setting::integer FROM pg_settings WHERE name = 'lock_timeout'"
713
+ )
687
714
  begin
688
715
  connection.execute("SET lock_timeout = #{PG::Connection.escape_string((timeout_seconds * 1000).to_s)};")
689
716
  block.call
690
717
  ensure
691
718
  begin
692
- connection.execute("SET lock_timeout = #{original_timeout};")
719
+ connection.execute("SET lock_timeout = #{original_timeout_ms};")
693
720
  rescue ActiveRecord::StatementInvalid => e
694
721
  if e.message =~ /PG::InFailedSqlTransaction/
695
722
  # If we're in a failed transaction the `SET lock_timeout` will be rolled back,
@@ -703,16 +730,18 @@ module PgHaMigrations::SafeStatements
703
730
 
704
731
  def adjust_statement_timeout(timeout_seconds, &block)
705
732
  _check_postgres_adapter!
706
- original_timeout = ActiveRecord::Base.value_from_sql("SHOW statement_timeout").sub(/s\Z/, '').to_i * 1000
733
+ original_timeout_ms = ActiveRecord::Base.value_from_sql(
734
+ "SELECT setting::integer FROM pg_settings WHERE name = 'statement_timeout'"
735
+ )
707
736
  begin
708
737
  connection.execute("SET statement_timeout = #{PG::Connection.escape_string((timeout_seconds * 1000).to_s)};")
709
738
  block.call
710
739
  ensure
711
740
  begin
712
- connection.execute("SET statement_timeout = #{original_timeout};")
741
+ connection.execute("SET statement_timeout = #{original_timeout_ms};")
713
742
  rescue ActiveRecord::StatementInvalid => e
714
743
  if e.message =~ /PG::InFailedSqlTransaction/
715
- # If we're in a failed transaction the `SET lock_timeout` will be rolled back,
744
+ # If we're in a failed transaction the `SET statement_timeout` will be rolled back,
716
745
  # so we don't need to worry about cleaning up, and we can't execute SQL anyway.
717
746
  else
718
747
  raise e
@@ -77,7 +77,7 @@ module PgHaMigrations::UnsafeStatements
77
77
  # Otherwise, direct dispatch to underlying Rails method without dependent object check / locking
78
78
  disable_or_delegate_default_method :add_check_constraint, ":add_check_constraint is NOT SAFE! Use :safe_add_unvalidated_check_constraint and then :safe_validate_check_constraint instead"
79
79
  disable_or_delegate_default_method :add_column, ":add_column is NOT SAFE! Use safe_add_column instead"
80
- disable_or_delegate_default_method :add_foreign_key, ":add_foreign_key is NOT SAFE! Explicitly call :unsafe_add_foreign_key"
80
+ disable_or_delegate_default_method :add_foreign_key, ":add_foreign_key is NOT SAFE! Use safe_add_foreign_key instead"
81
81
  disable_or_delegate_default_method :add_index, ":add_index is NOT SAFE! Use safe_add_concurrent_index instead"
82
82
  disable_or_delegate_default_method :change_column, ":change_column is NOT SAFE! Use a combination of safe and explicit unsafe migration methods instead"
83
83
  disable_or_delegate_default_method :change_column_default, ":change_column_default is NOT SAFE! Use safe_change_column_default instead"
@@ -1,3 +1,3 @@
1
1
  module PgHaMigrations
2
- VERSION = "2.1.1"
2
+ VERSION = "2.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_ha_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - celeen
@@ -13,7 +13,7 @@ authors:
13
13
  - redneckbeard
14
14
  bindir: exe
15
15
  cert_chain: []
16
- date: 2025-11-18 00:00:00.000000000 Z
16
+ date: 2026-01-08 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: rake