Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/nandi.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# frozen_string_literal: true

require "nandi/config"
require "nandi/renderers"
require "nandi/compiled_migration"
require "active_support/core_ext/string/inflections"
require "active_support/deprecation"
Expand Down
15 changes: 5 additions & 10 deletions lib/nandi/config.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require "nandi/migration_modifiers"
require "nandi/renderers"
require "nandi/renderers/renderer"
require "nandi/lockfile"
require "nandi/multi_database"

Expand All @@ -10,12 +10,6 @@ class Config
DEFAULT_COMPILE_FILES = "all"
DEFAULT_LOCKFILE_DIRECTORY = File.join(Dir.pwd, "db")

# The rendering backend used to produce output. The only supported option
# at current is Nandi::Renderers::ActiveRecord, which produces ActiveRecord
# migrations.
# @return [Class]
attr_accessor :renderer

# The files to compile when the compile generator is run. Default: `all`
# May be one of the following:
# - 'all' compiles all files
Expand All @@ -33,8 +27,7 @@ class Config
# @api private
attr_reader :post_processor, :custom_methods, :migration_modifiers

def initialize(renderer: Renderers::ActiveRecord)
@renderer = renderer
def initialize
@custom_methods = {}
@compile_files = DEFAULT_COMPILE_FILES
@lockfile_directory = DEFAULT_LOCKFILE_DIRECTORY
Expand Down Expand Up @@ -76,6 +69,7 @@ def lockfile_path(database_name = nil)

# Explicitly define getters for backwards compatibility when the database isnt specified.
# rubocop:disable Layout/LineLength
def renderer(database_name = nil) = config(database_name).renderer
def migration_directory(database_name = nil) = config(database_name).migration_directory
def output_directory(database_name = nil) = config(database_name).output_directory
def access_exclusive_lock_timeout(database_name = nil) = config(database_name).access_exclusive_lock_timeout
Expand All @@ -89,7 +83,8 @@ def concurrent_statement_timeout(database_name = nil) = config(database_name).co
# rubocop:enable Layout/LineLength

# Delegate setter methods to the default database for backwards compatibility
delegate :migration_directory=,
delegate :renderer=,
:migration_directory=,
:output_directory=,
:access_exclusive_lock_timeout=,
:access_exclusive_lock_timeout_max=,
Expand Down
1 change: 1 addition & 0 deletions lib/nandi/instructions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require "nandi/instructions/validate_constraint"
require "nandi/instructions/add_check_constraint"
require "nandi/instructions/irreversible_migration"
require "nandi/yugabyte/instructions/add_index_yb"

module Nandi
module Instructions; end
Expand Down
4 changes: 4 additions & 0 deletions lib/nandi/instructions/add_column.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def procedure
def lock
Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE
end

def validator
Validation::AddColumnValidator
end
end
end
end
4 changes: 4 additions & 0 deletions lib/nandi/instructions/add_index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ def lock
Nandi::Migration::LockWeights::SHARE
end

def validator
Validation::AddIndexValidator
end

attr_reader :table, :fields

private
Expand Down
4 changes: 4 additions & 0 deletions lib/nandi/instructions/add_reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def procedure
def lock
Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE
end

def validator
Validation::AddReferenceValidator
end
end
end
end
4 changes: 4 additions & 0 deletions lib/nandi/instructions/remove_index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def lock
Nandi::Migration::LockWeights::SHARE
end

def validator
Validation::RemoveIndexValidator
end

attr_reader :table

private
Expand Down
187 changes: 7 additions & 180 deletions lib/nandi/migration.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

require "nandi/migration/abstract_base"
require "nandi/instructions"
require "nandi/validator"
require "nandi/validation/failure_helpers"

module Nandi
# @abstract A migration must implement #up (the forward migration), and may
Expand All @@ -25,93 +24,13 @@ module Nandi
# drop_table :widgets
# end
# end
class Migration
include Nandi::Validation::FailureHelpers
class Migration < AbstractBaseMigration
# Avoids a circular dependency as we want Nandi::Migration to inherit from Nandi::Migration::AbstractBase
AbstractBase = AbstractBaseMigration

module LockWeights
ACCESS_EXCLUSIVE = 1
SHARE = 0
end

class InstructionSet < SimpleDelegator
def strictest_lock
return LockWeights::SHARE if empty?

map { |i| i.respond_to?(:lock) ? i.lock : LockWeights::ACCESS_EXCLUSIVE }.max
end
end

class << self
attr_reader :lock_timeout, :statement_timeout

# For sake both of correspondence with Postgres syntax and familiarity
# with activerecord-safe_migrations's identically named macros, we
# disable this cop.

# rubocop:disable Naming/AccessorMethodName

# Override the default lock timeout for the duration of the migration.
# This may be helpful when making changes to very busy tables, when a
# lock is less likely to be immediately available.
# @param timeout [Integer] New lock timeout in ms
def set_lock_timeout(timeout)
@lock_timeout = timeout
end

# Override the default statement timeout for the duration of the migration.
# This may be helpful when making changes that are likely to take a lot
# of time, like adding a new index on a large table.
# @param timeout [Integer] New lock timeout in ms
def set_statement_timeout(timeout)
@statement_timeout = timeout
end
# rubocop:enable Naming/AccessorMethodName
end

# @param validator [Nandi::Validator]
# @param database_name [Symbol, nil] The database this migration is being compiled
# for. Used to resolve per-database config. Defaults to the default database.
def initialize(validator, database_name: nil)
@validator = validator
@database_name = database_name
@instructions = Hash.new { |h, k| h[k] = InstructionSet.new([]) }
validate
end

# @api private
attr_reader :database_name

# @api private
def up_instructions
compile_instructions(:up)
end

# @api private
def down_instructions
compile_instructions(:down)
end

# The current lock timeout.
def lock_timeout
self.class.lock_timeout || default_lock_timeout
end

# The current statement timeout.
def statement_timeout
self.class.statement_timeout || default_statement_timeout
end

# @api private
def strictest_lock
@instructions.values.map(&:strictest_lock).max
end

# @abstract
def up
raise NotImplementedError
end

def down; end
# Use this declaration instead of Migration::Postgres so existing migrations default to using the Postgres adapter
# without naming changes
Postgres = self # rubocop:disable Naming/ConstantName

# Adds a new index to the database.
#
Expand Down Expand Up @@ -310,97 +229,5 @@ def change_column_default(table, column, value)
value: value,
)
end

# Raises an `ActiveRecord::IrreversibleMigration` error for use in
# irreversible migrations
def irreversible_migration
current_instructions << Instructions::IrreversibleMigration.new
end

# @api private
def compile_instructions(direction)
@direction = direction

public_send(direction) unless current_instructions.any?

Nandi.config.migration_modifiers.each { |mod| mod.public_send(direction, current_instructions) }

current_instructions
end

# @api private
def validate
validator.call(self)
rescue NotImplementedError => e
Validation::Result.new << failure(e.message)
end

def disable_lock_timeout?
if self.class.lock_timeout.nil?
strictest_lock == LockWeights::SHARE && Nandi.config.concurrent_lock_timeout(database_name).nil?
else
false
end
end

def disable_statement_timeout?
if self.class.statement_timeout.nil?
strictest_lock == LockWeights::SHARE && Nandi.config.concurrent_statement_timeout(database_name).nil?
else
false
end
end

def name
self.class.name
end

def respond_to_missing?(name)
Nandi.config.custom_methods.key?(name) || super
end

def mixins
(up_instructions + down_instructions).inject([]) do |mixins, i|
i.respond_to?(:mixins) ? [*mixins, *i.mixins] : mixins
end.uniq
end

def method_missing(name, ...)
if Nandi.config.custom_methods.key?(name)
invoke_custom_method(name, ...)
else
super
end
end

private

attr_reader :validator

def current_instructions
@instructions[@direction]
end

def default_statement_timeout
if strictest_lock == LockWeights::SHARE
Nandi.config.concurrent_statement_timeout(database_name) ||
Nandi.config.access_exclusive_statement_timeout(database_name)
else
Nandi.config.access_exclusive_statement_timeout(database_name)
end
end

def default_lock_timeout
if strictest_lock == LockWeights::SHARE
Nandi.config.concurrent_lock_timeout(database_name) || Nandi.config.access_exclusive_lock_timeout(database_name)
else
Nandi.config.access_exclusive_lock_timeout(database_name)
end
end

def invoke_custom_method(name, ...)
klass = Nandi.config.custom_methods[name]
current_instructions << klass.new(...)
end
end
end
Loading
Loading