module ActiveRecord::PGExtensions::PessimisticMigrations
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)
Public Instance Methods
add_foreign_key(from_table, to_table, delay_validation: false, if_not_exists: false, **options)
click to toggle source
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
Calls superclass method
# File lib/active_record/pg_extensions/pessimistic_migrations.rb, line 46 def add_foreign_key(from_table, to_table, delay_validation: false, if_not_exists: false, **options) # pointless if we're in a transaction delay_validation = false if open_transactions.positive? options[:validate] = false if delay_validation options = foreign_key_options(from_table, to_table, options) if if_not_exists || delay_validation scope = quoted_scope(options[: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, **options) unless valid == false validate_constraint(from_table, options[:name]) if delay_validation end
add_index(table_name, column_name, options = {})
click to toggle source
will automatically remove a NOT VALID index before trying to add
Calls superclass method
# File lib/active_record/pg_extensions/pessimistic_migrations.rb, line 66 def add_index(table_name, column_name, options = {}) # catch a concurrent index add that fails because it already exists, and is invalid if options[:algorithm] == :concurrently || options[:if_not_exists] column_names = index_column_names(column_name) index_name = options[:name].to_s if options.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 if valid == false && options[:algorithm] == :concurrently remove_index(table_name, name: index_name, algorithm: :concurrently) end return if options[:if_not_exists] && valid == true end # Rails.version: can stop doing this in Rails 6.2, when it's natively supported options.delete(:if_not_exists) super end
change_column_null(table, column, nullness, default = nil)
click to toggle source
adds a temporary check constraint to reduce locking when changing to NOT NULL, and we're not in a transaction
Calls superclass method
# 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 temp_constraint_name = "chk_rails_#{table}_#{column}_not_null" 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 ActiveRecord::StatementInvalid => e raise ActiveRecord::NotNullViolation.new(sql: e.sql, binds: e.binds) if e.cause.is_a?(PG::CheckViolation) raise end transaction do super remove_check_constraint(table, name: temp_constraint_name) end end