Class: Nandi::Migration Abstract
- Inherits:
-
Object
- Object
- Nandi::Migration
- Includes:
- Validation::FailureHelpers
- Defined in:
- lib/nandi/migration.rb
Overview
A migration must implement #up (the forward migration), and may also implement #down (the rollback sequence).
The base class for migrations; Nandi’s equivalent of ActiveRecord::Migration. All the statements in the migration are statically analysed together to rule out migrations with a high risk of causing availability issues. Additionally, our implementations of some statements will rule out certain common footguns (for example, creating an index without using the ‘CONCURRENTLY` parameter.)
Defined Under Namespace
Modules: LockWeights Classes: InstructionSet
Class Attribute Summary collapse
-
.lock_timeout ⇒ Object
readonly
Returns the value of attribute lock_timeout.
-
.statement_timeout ⇒ Object
readonly
Returns the value of attribute statement_timeout.
Class Method Summary collapse
-
.set_lock_timeout(timeout) ⇒ Object
Override the default lock timeout for the duration of the migration.
-
.set_statement_timeout(timeout) ⇒ Object
Override the default statement timeout for the duration of the migration.
Instance Method Summary collapse
-
#add_check_constraint(table, name, check) ⇒ Object
Add a check constraint, in the NOT VALID state.
-
#add_column(table, name, type, **kwargs) ⇒ Object
Adds a new column.
-
#add_foreign_key(table, target, column: nil, name: nil) ⇒ Object
Add a foreign key constraint.
-
#add_index(table, fields, **kwargs) ⇒ Object
Adds a new index to the database.
-
#change_column_default(table, column, value) ⇒ Object
Changes the default value for this column when new rows are inserted into the table.
- #compile_instructions(direction) ⇒ Object private
-
#create_table(table, **kwargs) {|columns_reader| ... } ⇒ Object
Creates a new table.
- #disable_lock_timeout? ⇒ Boolean
- #disable_statement_timeout? ⇒ Boolean
- #down ⇒ Object
- #down_instructions ⇒ Object private
-
#drop_constraint(table, name) ⇒ Object
Drops an existing constraint.
-
#drop_table(table) ⇒ Object
Drops an existing table.
-
#initialize(validator) ⇒ Migration
constructor
A new instance of Migration.
-
#irreversible_migration ⇒ Object
Raises an ‘ActiveRecord::IrreversibleMigration` error for use in irreversible migrations.
-
#lock_timeout ⇒ Object
The current lock timeout.
- #method_missing(name, *args, &block) ⇒ Object
- #mixins ⇒ Object
- #name ⇒ Object
-
#remove_column(table, name, **extra_args) ⇒ Object
Remove an existing column.
-
#remove_index(table, target) ⇒ Object
Drop an index from the database.
-
#remove_not_null_constraint(table, column) ⇒ Object
Drops an existing NOT NULL constraint.
- #respond_to_missing?(name) ⇒ Boolean
-
#statement_timeout ⇒ Object
The current statement timeout.
- #strictest_lock ⇒ Object private
- #up ⇒ Object abstract
- #up_instructions ⇒ Object private
- #validate ⇒ Object private
-
#validate_constraint(table, name) ⇒ Object
Validates an existing foreign key constraint.
Methods included from Validation::FailureHelpers
#assert, #collect_errors, #failure, #success
Constructor Details
#initialize(validator) ⇒ Migration
Returns a new instance of Migration.
71 72 73 74 75 |
# File 'lib/nandi/migration.rb', line 71 def initialize(validator) @validator = validator @instructions = Hash.new { |h, k| h[k] = InstructionSet.new([]) } validate end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
Class Attribute Details
.lock_timeout ⇒ Object (readonly)
Returns the value of attribute lock_timeout.
45 46 47 |
# File 'lib/nandi/migration.rb', line 45 def lock_timeout @lock_timeout end |
.statement_timeout ⇒ Object (readonly)
Returns the value of attribute statement_timeout.
45 46 47 |
# File 'lib/nandi/migration.rb', line 45 def statement_timeout @statement_timeout end |
Class Method Details
.set_lock_timeout(timeout) ⇒ Object
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.
56 57 58 |
# File 'lib/nandi/migration.rb', line 56 def set_lock_timeout(timeout) @lock_timeout = timeout end |
.set_statement_timeout(timeout) ⇒ Object
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.
64 65 66 |
# File 'lib/nandi/migration.rb', line 64 def set_statement_timeout(timeout) @statement_timeout = timeout end |
Instance Method Details
#add_check_constraint(table, name, check) ⇒ Object
Add a check constraint, in the NOT VALID state.
228 229 230 231 232 233 234 |
# File 'lib/nandi/migration.rb', line 228 def add_check_constraint(table, name, check) current_instructions << Instructions::AddCheckConstraint.new( table: table, name: name, check: check, ) end |
#add_column(table, name, type, **kwargs) ⇒ Object
Adds a new column. Nandi will explicitly set the column to be NULL, as validating a new NOT NULL constraint can be very expensive on large tables and cause availability issues.
182 183 184 185 186 187 188 189 |
# File 'lib/nandi/migration.rb', line 182 def add_column(table, name, type, **kwargs) current_instructions << Instructions::AddColumn.new( table: table, name: name, type: type, **kwargs, ) end |
#add_foreign_key(table, target, column: nil, name: nil) ⇒ Object
Add a foreign key constraint. The generated SQL will include the NOT VALID parameter, which will prevent immediate validation of the constraint, which locks the target table for writes potentially for a long time. Use the separate #validate_constraint method, in a separate migration; this only takes a row-level lock as it scans through.
215 216 217 218 219 220 221 222 |
# File 'lib/nandi/migration.rb', line 215 def add_foreign_key(table, target, column: nil, name: nil) current_instructions << Instructions::AddForeignKey.new( table: table, target: target, column: column, name: name, ) end |
#add_index(table, fields, **kwargs) ⇒ Object
Adds a new index to the database.
Nandi will:
-
add the ‘CONCURRENTLY` option, which means the change takes a less restrictive lock at the cost of not running in a DDL transaction
-
use the ‘BTREE` index type which is the safest to create.
Because index creation is particularly failure-prone, and because we cannot run in a transaction and therefore risk partially applied migrations that (in a Rails environment) require manual intervention, Nandi Validates that, if there is a add_index statement in the migration, it must be the only statement.
126 127 128 129 130 131 132 |
# File 'lib/nandi/migration.rb', line 126 def add_index(table, fields, **kwargs) current_instructions << Instructions::AddIndex.new( **kwargs, table: table, fields: fields, ) end |
#change_column_default(table, column, value) ⇒ Object
Changes the default value for this column when new rows are inserted into the table.
274 275 276 277 278 279 280 |
# File 'lib/nandi/migration.rb', line 274 def change_column_default(table, column, value) current_instructions << Instructions::ChangeColumnDefault.new( table: table, column: column, value: value, ) end |
#compile_instructions(direction) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
289 290 291 292 293 294 295 |
# File 'lib/nandi/migration.rb', line 289 def compile_instructions(direction) @direction = direction public_send(direction) unless current_instructions.any? current_instructions end |
#create_table(table, **kwargs) {|columns_reader| ... } ⇒ Object
Creates a new table. Yields a ColumnsReader object as a block, to allow adding columns.
161 162 163 164 165 166 167 |
# File 'lib/nandi/migration.rb', line 161 def create_table(table, **kwargs, &block) current_instructions << Instructions::CreateTable.new( **kwargs, table: table, columns_block: block, ) end |
#disable_lock_timeout? ⇒ Boolean
304 305 306 307 308 309 310 |
# File 'lib/nandi/migration.rb', line 304 def disable_lock_timeout? if self.class.lock_timeout.nil? strictest_lock == LockWeights::SHARE else false end end |
#disable_statement_timeout? ⇒ Boolean
312 313 314 315 316 317 318 |
# File 'lib/nandi/migration.rb', line 312 def disable_statement_timeout? if self.class.statement_timeout.nil? strictest_lock == LockWeights::SHARE else false end end |
#down ⇒ Object
107 |
# File 'lib/nandi/migration.rb', line 107 def down; end |
#down_instructions ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
83 84 85 |
# File 'lib/nandi/migration.rb', line 83 def down_instructions compile_instructions(:down) end |
#drop_constraint(table, name) ⇒ Object
Drops an existing constraint.
249 250 251 252 253 254 |
# File 'lib/nandi/migration.rb', line 249 def drop_constraint(table, name) current_instructions << Instructions::DropConstraint.new( table: table, name: name, ) end |
#drop_table(table) ⇒ Object
Drops an existing table
171 172 173 |
# File 'lib/nandi/migration.rb', line 171 def drop_table(table) current_instructions << Instructions::DropTable.new(table: table) end |
#irreversible_migration ⇒ Object
Raises an ‘ActiveRecord::IrreversibleMigration` error for use in irreversible migrations
284 285 286 |
# File 'lib/nandi/migration.rb', line 284 def irreversible_migration current_instructions << Instructions::IrreversibleMigration.new end |
#lock_timeout ⇒ Object
The current lock timeout.
88 89 90 |
# File 'lib/nandi/migration.rb', line 88 def lock_timeout self.class.lock_timeout || default_lock_timeout end |
#mixins ⇒ Object
328 329 330 331 332 |
# File 'lib/nandi/migration.rb', line 328 def mixins (up_instructions + down_instructions).inject([]) do |mixins, i| i.respond_to?(:mixins) ? [*mixins, *i.mixins] : mixins end.uniq end |
#name ⇒ Object
320 321 322 |
# File 'lib/nandi/migration.rb', line 320 def name self.class.name end |
#remove_column(table, name, **extra_args) ⇒ Object
Remove an existing column.
196 197 198 199 200 201 202 |
# File 'lib/nandi/migration.rb', line 196 def remove_column(table, name, **extra_args) current_instructions << Instructions::RemoveColumn.new( **extra_args, table: table, name: name, ) end |
#remove_index(table, target) ⇒ Object
Drop an index from the database.
Nandi will add the ‘CONCURRENTLY` option, which means the change takes a less restrictive lock at the cost of not running in a DDL transaction.
Because we cannot run in a transaction and therefore risk partially applied migrations that (in a Rails environment) require manual intervention, Nandi Validates that, if there is a remove_index statement in the migration, it must be the only statement.
149 150 151 |
# File 'lib/nandi/migration.rb', line 149 def remove_index(table, target) current_instructions << Instructions::RemoveIndex.new(table: table, field: target) end |
#remove_not_null_constraint(table, column) ⇒ Object
Drops an existing NOT NULL constraint. Please note that this migration is not safely reversible; to enforce NOT NULL like behaviour, use a CHECK constraint and validate it in a separate migration.
262 263 264 265 266 267 |
# File 'lib/nandi/migration.rb', line 262 def remove_not_null_constraint(table, column) current_instructions << Instructions::RemoveNotNullConstraint.new( table: table, column: column, ) end |
#respond_to_missing?(name) ⇒ Boolean
324 325 326 |
# File 'lib/nandi/migration.rb', line 324 def respond_to_missing?(name) Nandi.config.custom_methods.key?(name) || super end |
#statement_timeout ⇒ Object
The current statement timeout.
93 94 95 |
# File 'lib/nandi/migration.rb', line 93 def statement_timeout self.class.statement_timeout || default_statement_timeout end |
#strictest_lock ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
98 99 100 |
# File 'lib/nandi/migration.rb', line 98 def strictest_lock @instructions.values.map(&:strictest_lock).max end |
#up ⇒ Object
103 104 105 |
# File 'lib/nandi/migration.rb', line 103 def up raise NotImplementedError end |
#up_instructions ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
78 79 80 |
# File 'lib/nandi/migration.rb', line 78 def up_instructions compile_instructions(:up) end |
#validate ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
298 299 300 301 302 |
# File 'lib/nandi/migration.rb', line 298 def validate validator.call(self) rescue NotImplementedError => e Validation::Result.new << failure(e.) end |
#validate_constraint(table, name) ⇒ Object
Validates an existing foreign key constraint.
239 240 241 242 243 244 |
# File 'lib/nandi/migration.rb', line 239 def validate_constraint(table, name) current_instructions << Instructions::ValidateConstraint.new( table: table, name: name, ) end |