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.
-
#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) ⇒ 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, **kwargs, &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.
-
#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) ⇒ Migration
Returns a new instance of Migration.
72 73 74 75 76 |
# File 'lib/nandi/migration.rb', line 72 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.
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.
254 255 256 257 258 259 260 |
# File 'lib/nandi/migration.rb', line 254 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.
183 184 185 186 187 188 189 190 |
# File 'lib/nandi/migration.rb', line 183 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.
241 242 243 244 245 246 247 248 |
# File 'lib/nandi/migration.rb', line 241 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
-
default to the ‘BTREE` index 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.
127 128 129 130 131 132 133 |
# File 'lib/nandi/migration.rb', line 127 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!
197 198 199 200 201 202 203 |
# File 'lib/nandi/migration.rb', line 197 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.
300 301 302 303 304 305 306 |
# File 'lib/nandi/migration.rb', line 300 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.
315 316 317 318 319 320 321 |
# File 'lib/nandi/migration.rb', line 315 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.
162 163 164 165 166 167 168 |
# File 'lib/nandi/migration.rb', line 162 def create_table(table, **kwargs, &block) current_instructions << Instructions::CreateTable.new( **kwargs, table: table, columns_block: block, ) end |
#disable_lock_timeout? ⇒ Boolean
330 331 332 333 334 335 336 |
# File 'lib/nandi/migration.rb', line 330 def disable_lock_timeout? if self.class.lock_timeout.nil? strictest_lock == LockWeights::SHARE else false end end |
#disable_statement_timeout? ⇒ Boolean
338 339 340 341 342 343 344 |
# File 'lib/nandi/migration.rb', line 338 def disable_statement_timeout? if self.class.statement_timeout.nil? strictest_lock == LockWeights::SHARE else false end end |
#down ⇒ Object
108 |
# File 'lib/nandi/migration.rb', line 108 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.
84 85 86 |
# File 'lib/nandi/migration.rb', line 84 def down_instructions compile_instructions(:down) end |
#drop_constraint(table, name) ⇒ Object
Drops an existing constraint.
275 276 277 278 279 280 |
# File 'lib/nandi/migration.rb', line 275 def drop_constraint(table, name) current_instructions << Instructions::DropConstraint.new( table: table, name: name, ) end |
#drop_table(table) ⇒ Object
Drops an existing table
172 173 174 |
# File 'lib/nandi/migration.rb', line 172 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
310 311 312 |
# File 'lib/nandi/migration.rb', line 310 def irreversible_migration current_instructions << Instructions::IrreversibleMigration.new end |
#lock_timeout ⇒ Object
The current lock timeout.
89 90 91 |
# File 'lib/nandi/migration.rb', line 89 def lock_timeout self.class.lock_timeout || default_lock_timeout end |
#mixins ⇒ Object
354 355 356 357 358 |
# File 'lib/nandi/migration.rb', line 354 def mixins (up_instructions + down_instructions).inject([]) do |mixins, i| i.respond_to?(:mixins) ? [*mixins, *i.mixins] : mixins end.uniq end |
#name ⇒ Object
346 347 348 |
# File 'lib/nandi/migration.rb', line 346 def name self.class.name end |
#remove_column(table, name, **extra_args) ⇒ Object
Remove an existing column.
222 223 224 225 226 227 228 |
# File 'lib/nandi/migration.rb', line 222 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.
150 151 152 |
# File 'lib/nandi/migration.rb', line 150 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.
288 289 290 291 292 293 |
# File 'lib/nandi/migration.rb', line 288 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.
209 210 211 212 213 214 215 |
# File 'lib/nandi/migration.rb', line 209 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
350 351 352 |
# File 'lib/nandi/migration.rb', line 350 def respond_to_missing?(name) Nandi.config.custom_methods.key?(name) || super end |
#statement_timeout ⇒ Object
The current statement timeout.
94 95 96 |
# File 'lib/nandi/migration.rb', line 94 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.
99 100 101 |
# File 'lib/nandi/migration.rb', line 99 def strictest_lock @instructions.values.map(&:strictest_lock).max end |
#up ⇒ Object
104 105 106 |
# File 'lib/nandi/migration.rb', line 104 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.
79 80 81 |
# File 'lib/nandi/migration.rb', line 79 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.
324 325 326 327 328 |
# File 'lib/nandi/migration.rb', line 324 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.
265 266 267 268 269 270 |
# File 'lib/nandi/migration.rb', line 265 def validate_constraint(table, name) current_instructions << Instructions::ValidateConstraint.new( table: table, name: name, ) end |