Module: ActiveRecord::PGExtensions::PessimisticMigrations
- Defined in:
- lib/active_record/pg_extensions/pessimistic_migrations.rb
Overview
Changes several DDL commands to trigger background queries to warm caches prior to executing, in order to reduce the amount of time the actual DDL takes to execute (and thus how long it needs the lock)
Instance Method Summary collapse
- #add_check_constraint(table_name, expression, if_not_exists: false, **options) ⇒ Object
-
#add_foreign_key(from_table, to_table, delay_validation: false, if_not_exists: false, **options) ⇒ Object
several improvements: * support if_not_exists * delay_validation automatically creates the FK as NOT VALID, and then immediately validates it * if delay_validation is used, and the index already exists but is NOT VALID, it just re-tries the validation, instead of failing.
-
#add_index(table_name, column_name, **options) ⇒ Object
will automatically remove a NOT VALID index before trying to add.
-
#change_column_null(table, column, nullness, default = nil) ⇒ Object
adds a temporary check constraint to reduce locking when changing to NOT NULL, and we’re not in a transaction.
- #remove_check_constraint(table_name, expression = nil, if_exists: false, **options) ⇒ Object
Instance Method Details
#add_check_constraint(table_name, expression, if_not_exists: false, **options) ⇒ Object
92 93 94 95 96 97 |
# File 'lib/active_record/pg_extensions/pessimistic_migrations.rb', line 92 def add_check_constraint(table_name, expression, if_not_exists: false, **) = (table_name, expression, ) return if if_not_exists && check_constraint_for(table_name, **) super end |
#add_foreign_key(from_table, to_table, delay_validation: false, if_not_exists: false, **options) ⇒ Object
several improvements:
* support if_not_exists
* delay_validation automatically creates the FK as NOT VALID, and then immediately validates it
* if delay_validation is used, and the index already exists but is NOT VALID, it just re-tries
the validation, instead of failing
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/active_record/pg_extensions/pessimistic_migrations.rb', line 45 def add_foreign_key(from_table, to_table, delay_validation: false, if_not_exists: false, **) # pointless if we're in a transaction delay_validation = false if open_transactions.positive? [:validate] = false if delay_validation = (from_table, to_table, ) if if_not_exists || delay_validation scope = quoted_scope([:name]) valid = select_value(<<~SQL, "SCHEMA") SELECT convalidated FROM pg_constraint INNER JOIN pg_namespace ON pg_namespace.oid=connamespace WHERE conname=#{scope[:name]} AND nspname=#{scope[:schema]} SQL return if valid == true && if_not_exists end super(from_table, to_table, **) unless valid == false validate_constraint(from_table, [:name]) if delay_validation end |
#add_index(table_name, column_name, **options) ⇒ Object
will automatically remove a NOT VALID index before trying to add
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/active_record/pg_extensions/pessimistic_migrations.rb', line 65 def add_index(table_name, column_name, **) # catch a concurrent index add that fails because it already exists, and is invalid if [:algorithm] == :concurrently && [:if_not_exists] column_names = index_column_names(column_name) index_name = [:name].to_s if .key?(:name) index_name ||= index_name(table_name, column_names) index = quoted_scope(index_name) table = quoted_scope(table_name) valid = select_value(<<~SQL, "SCHEMA") SELECT indisvalid FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid INNER JOIN pg_class i ON d.indexrelid = i.oid WHERE i.relkind = 'i' AND i.relname = #{index[:name]} AND t.relname = #{table[:name]} AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{index[:schema]} ) LIMIT 1 SQL return if valid == true remove_index(table_name, name: index_name, algorithm: :concurrently) if valid == false end super end |
#change_column_null(table, column, nullness, default = nil) ⇒ Object
adds a temporary check constraint to reduce locking when changing to NOT NULL, and we’re not in a transaction
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/active_record/pg_extensions/pessimistic_migrations.rb', line 10 def change_column_null(table, column, nullness, default = nil) # no point in doing extra work to avoid locking if we're already in a transaction return super if nullness != false || open_transactions.positive? return if columns(table).find { |c| c.name == column.to_s }&.null == false # PG identifiers max out at 63 characters temp_constraint_name = "chk_rails_#{table}_#{column}_not_null"[0...63] scope = quoted_scope(temp_constraint_name) # check for temp constraint valid = select_value(<<~SQL, "SCHEMA") SELECT convalidated FROM pg_constraint INNER JOIN pg_namespace ON pg_namespace.oid=connamespace WHERE conname=#{scope[:name]} AND nspname=#{scope[:schema]} SQL if valid.nil? add_check_constraint(table, "#{quote_column_name(column)} IS NOT NULL", name: temp_constraint_name, validate: false) end begin validate_constraint(table, temp_constraint_name) rescue PG::CheckViolation => e raise ActiveRecord::NotNullViolation.new(sql: e.sql, binds: e.binds) end transaction do super remove_check_constraint(table, name: temp_constraint_name) end end |
#remove_check_constraint(table_name, expression = nil, if_exists: false, **options) ⇒ Object
100 101 102 103 104 105 |
# File 'lib/active_record/pg_extensions/pessimistic_migrations.rb', line 100 def remove_check_constraint(table_name, expression = nil, if_exists: false, **) = (table_name, expression, ) return if if_exists && !check_constraint_for(table_name, **) super end |