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.
Instance Attribute Summary collapse
- #database_name ⇒ Object readonly private
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.
-
#add_reference(table, ref_name, **kwargs) ⇒ Object
Adds a new reference column.
-
#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, database_name: nil) ⇒ Migration
constructor
A new instance of Migration.
-
#irreversible_migration ⇒ Object
Raises an
ActiveRecord::IrreversibleMigrationerror for use in irreversible migrations. -
#lock_timeout ⇒ Object
The current lock timeout.
- #method_missing(name) ⇒ 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.
-
#remove_reference(table, ref_name, **kwargs) ⇒ Object
Removes a reference column.
- #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, database_name: nil) ⇒ Migration
Returns a new instance of Migration.
74 75 76 77 78 79 |
# File 'lib/nandi/migration.rb', line 74 def initialize(validator, database_name: nil) @validator = validator @database_name = database_name @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 |
Instance Attribute Details
#database_name ⇒ Object (readonly)
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.
82 83 84 |
# File 'lib/nandi/migration.rb', line 82 def database_name @database_name 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.
57 58 59 |
# File 'lib/nandi/migration.rb', line 57 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.
65 66 67 |
# File 'lib/nandi/migration.rb', line 65 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.
260 261 262 263 264 265 266 |
# File 'lib/nandi/migration.rb', line 260 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.
189 190 191 192 193 194 195 196 |
# File 'lib/nandi/migration.rb', line 189 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.
247 248 249 250 251 252 253 254 |
# File 'lib/nandi/migration.rb', line 247 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
CONCURRENTLYoption, which means the change takes a less restrictive lock at the cost of not running in a DDL transaction - default to the
BTREEindex type, as it is commonly a good fit.
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.
133 134 135 136 137 138 139 |
# File 'lib/nandi/migration.rb', line 133 def add_index(table, fields, **kwargs) current_instructions << Instructions::AddIndex.new( **kwargs, table: table, fields: fields, ) end |
#add_reference(table, ref_name, **kwargs) ⇒ Object
Adds a new reference column. Nandi will validate that the foreign key flag
is not set to true; use add_foreign_key and validate_foreign_key instead!
203 204 205 206 207 208 209 |
# File 'lib/nandi/migration.rb', line 203 def add_reference(table, ref_name, **kwargs) current_instructions << Instructions::AddReference.new( table: table, ref_name: ref_name, **kwargs, ) end |
#change_column_default(table, column, value) ⇒ Object
Changes the default value for this column when new rows are inserted into the table.
306 307 308 309 310 311 312 |
# File 'lib/nandi/migration.rb', line 306 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.
321 322 323 324 325 326 327 328 329 |
# File 'lib/nandi/migration.rb', line 321 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 |
#create_table(table, **kwargs) {|columns_reader| ... } ⇒ Object
Creates a new table. Yields a ColumnsReader object as a block, to allow adding columns.
168 169 170 171 172 173 174 |
# File 'lib/nandi/migration.rb', line 168 def create_table(table, **kwargs, &block) current_instructions << Instructions::CreateTable.new( **kwargs, table: table, columns_block: block, ) end |
#disable_lock_timeout? ⇒ Boolean
338 339 340 341 342 343 344 |
# File 'lib/nandi/migration.rb', line 338 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 |
#disable_statement_timeout? ⇒ Boolean
346 347 348 349 350 351 352 |
# File 'lib/nandi/migration.rb', line 346 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 |
#down ⇒ Object
114 |
# File 'lib/nandi/migration.rb', line 114 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.
90 91 92 |
# File 'lib/nandi/migration.rb', line 90 def down_instructions compile_instructions(:down) end |
#drop_constraint(table, name) ⇒ Object
Drops an existing constraint.
281 282 283 284 285 286 |
# File 'lib/nandi/migration.rb', line 281 def drop_constraint(table, name) current_instructions << Instructions::DropConstraint.new( table: table, name: name, ) end |
#drop_table(table) ⇒ Object
Drops an existing table
178 179 180 |
# File 'lib/nandi/migration.rb', line 178 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
316 317 318 |
# File 'lib/nandi/migration.rb', line 316 def irreversible_migration current_instructions << Instructions::IrreversibleMigration.new end |
#lock_timeout ⇒ Object
The current lock timeout.
95 96 97 |
# File 'lib/nandi/migration.rb', line 95 def lock_timeout self.class.lock_timeout || default_lock_timeout end |
#mixins ⇒ Object
362 363 364 365 366 |
# File 'lib/nandi/migration.rb', line 362 def mixins (up_instructions + down_instructions).inject([]) do |mixins, i| i.respond_to?(:mixins) ? [*mixins, *i.mixins] : mixins end.uniq end |
#name ⇒ Object
354 355 356 |
# File 'lib/nandi/migration.rb', line 354 def name self.class.name end |
#remove_column(table, name, **extra_args) ⇒ Object
Remove an existing column.
228 229 230 231 232 233 234 |
# File 'lib/nandi/migration.rb', line 228 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.
156 157 158 |
# File 'lib/nandi/migration.rb', line 156 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.
294 295 296 297 298 299 |
# File 'lib/nandi/migration.rb', line 294 def remove_not_null_constraint(table, column) current_instructions << Instructions::RemoveNotNullConstraint.new( table: table, column: column, ) end |
#remove_reference(table, ref_name, **kwargs) ⇒ Object
Removes a reference column.
215 216 217 218 219 220 221 |
# File 'lib/nandi/migration.rb', line 215 def remove_reference(table, ref_name, **kwargs) current_instructions << Instructions::RemoveReference.new( table: table, ref_name: ref_name, **kwargs, ) end |
#respond_to_missing?(name) ⇒ Boolean
358 359 360 |
# File 'lib/nandi/migration.rb', line 358 def respond_to_missing?(name) Nandi.config.custom_methods.key?(name) || super end |
#statement_timeout ⇒ Object
The current statement timeout.
100 101 102 |
# File 'lib/nandi/migration.rb', line 100 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.
105 106 107 |
# File 'lib/nandi/migration.rb', line 105 def strictest_lock @instructions.values.map(&:strictest_lock).max end |
#up ⇒ Object
110 111 112 |
# File 'lib/nandi/migration.rb', line 110 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.
85 86 87 |
# File 'lib/nandi/migration.rb', line 85 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.
332 333 334 335 336 |
# File 'lib/nandi/migration.rb', line 332 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.
271 272 273 274 275 276 |
# File 'lib/nandi/migration.rb', line 271 def validate_constraint(table, name) current_instructions << Instructions::ValidateConstraint.new( table: table, name: name, ) end |