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::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) ⇒ 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
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.
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 |