nandi 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +60 -0
- data/lib/nandi/config.rb +15 -9
- data/lib/nandi/instructions/add_foreign_key.rb +7 -2
- data/lib/nandi/migration.rb +2 -0
- data/lib/nandi/migration_modifiers/base.rb +10 -0
- data/lib/nandi/migration_modifiers/create_table_validates_fks.rb +20 -0
- data/lib/nandi/migration_modifiers.rb +9 -0
- data/lib/nandi/multi_database.rb +36 -16
- data/lib/nandi/timeout_policies/access_exclusive.rb +2 -2
- data/lib/nandi/timeout_policies/concurrent.rb +2 -2
- data/lib/nandi/validator.rb +2 -2
- data/lib/nandi/version.rb +1 -1
- data/lib/nandi.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 986532981584b1518a8422a2cc212ed963782fb31554012a0e2b4da50bf8a3db
|
|
4
|
+
data.tar.gz: 282b4f7c95256f6a8af1090b82a2324d0cc12e4570bf22608c219d8486cb5334
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7788cca1a5ca77b8e8e04decc6819b41078936452f3ab2cae00a3b35ca6c338f4fb4870e2d8eb11f4f3109029c41a2ccd327fb980417e1b95c1bf48644bca23e
|
|
7
|
+
data.tar.gz: 2331b4022d381b2863ecbf2429365aa2870fb39386fde1cb0aa27ea8800fc3c7777490896a2d55af95696d8b0457c2f485890f5dcd8878019f1a50b2ea28af09
|
data/README.md
CHANGED
|
@@ -473,6 +473,16 @@ Register a block to be called on output, for example a code formatter. Whatever
|
|
|
473
473
|
config.post_process { |migration| MyFormatter.format(migration) }
|
|
474
474
|
```
|
|
475
475
|
|
|
476
|
+
#register_migration_modifier(klass)
|
|
477
|
+
|
|
478
|
+
Register a custom migration modifier. Migration modifiers inherit from `Nandi::MigrationModifiers::Base` and implement `.up(instructions)`, `.down(instructions)`, or both. Nandi calls the appropriate method based on migration direction.
|
|
479
|
+
|
|
480
|
+
Nandi ships with one built-in modifier, `Nandi::MigrationModifiers::CreateTableValidatesFks`, which is enabled by default. See the [Migration Modifiers](#migration-modifiers) section for details.
|
|
481
|
+
|
|
482
|
+
```rb
|
|
483
|
+
config.register_migration_modifier(MyCustomModifier)
|
|
484
|
+
```
|
|
485
|
+
|
|
476
486
|
#register_method(name, klass)
|
|
477
487
|
|
|
478
488
|
Register a custom DDL method.
|
|
@@ -483,6 +493,56 @@ Parameters:
|
|
|
483
493
|
|
|
484
494
|
`klass` (Class) — The class to initialise with the arguments to the method. It should define a `template` instance method which will return a subclass of Cell::ViewModel from the Cells templating library and a `procedure` method that returns the name of the method. It may optionally define a `mixins` method, which will return an array of `Module`s to be mixed into any migration that uses this method.
|
|
485
495
|
|
|
496
|
+
## Migration Modifiers
|
|
497
|
+
|
|
498
|
+
Migration modifiers allow instructions to change their behaviour based on other instructions in the same migration. After the direction block runs, Nandi iterates over all configured modifiers and passes the full instruction list to each one.
|
|
499
|
+
|
|
500
|
+
### Built-in: `CreateTableValidatesFks`
|
|
501
|
+
|
|
502
|
+
When you create a table and add a foreign key on that same table in a single migration, the FK does not need the usual `NOT VALID` deferral — the table is brand new, so there are no existing rows to validate. Nandi detects this automatically and sets `validate: true` on the relevant FK instructions:
|
|
503
|
+
|
|
504
|
+
```rb
|
|
505
|
+
class CreatePaymentsWithFk < Nandi::Migration
|
|
506
|
+
def up
|
|
507
|
+
create_table :payments do |t|
|
|
508
|
+
t.bigint :mandate_id, null: false
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
add_foreign_key :payments, :mandates
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
The compiled output will include `validate: true` on the `add_foreign_key` call, skipping the separate validate step that would otherwise be required.
|
|
517
|
+
|
|
518
|
+
This modifier is enabled by default. To disable it, you would need to replace `config.migration_modifiers` entirely — there is no built-in opt-out.
|
|
519
|
+
|
|
520
|
+
### Writing a custom modifier
|
|
521
|
+
|
|
522
|
+
Inherit from `Nandi::MigrationModifiers::Base` and override `.up`, `.down`, or both. The base class provides no-op defaults so you only implement what you need:
|
|
523
|
+
|
|
524
|
+
```rb
|
|
525
|
+
class MyModifier < Nandi::MigrationModifiers::Base
|
|
526
|
+
def self.up(instructions)
|
|
527
|
+
# inspect and mutate instructions for the up direction
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def self.down(instructions)
|
|
531
|
+
# inspect and mutate instructions for the down direction
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Register it in your Nandi initializer:
|
|
537
|
+
|
|
538
|
+
```rb
|
|
539
|
+
Nandi.configure do |config|
|
|
540
|
+
config.register_migration_modifier(MyModifier)
|
|
541
|
+
end
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Modifiers are applied in registration order, after all built-in modifiers.
|
|
545
|
+
|
|
486
546
|
## Multi-Database Support
|
|
487
547
|
|
|
488
548
|
Nandi 2.0+ supports managing migrations for multiple databases within a single Rails application.
|
data/lib/nandi/config.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "nandi/migration_modifiers"
|
|
3
4
|
require "nandi/renderers"
|
|
4
5
|
require "nandi/lockfile"
|
|
5
6
|
require "nandi/multi_database"
|
|
@@ -30,13 +31,18 @@ module Nandi
|
|
|
30
31
|
attr_writer :lockfile_directory
|
|
31
32
|
|
|
32
33
|
# @api private
|
|
33
|
-
attr_reader :post_processor, :custom_methods
|
|
34
|
+
attr_reader :post_processor, :custom_methods, :migration_modifiers
|
|
34
35
|
|
|
35
36
|
def initialize(renderer: Renderers::ActiveRecord)
|
|
36
37
|
@renderer = renderer
|
|
37
38
|
@custom_methods = {}
|
|
38
39
|
@compile_files = DEFAULT_COMPILE_FILES
|
|
39
40
|
@lockfile_directory = DEFAULT_LOCKFILE_DIRECTORY
|
|
41
|
+
@migration_modifiers = [MigrationModifiers::CreateTableValidatesFks]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def register_migration_modifier(klass)
|
|
45
|
+
@migration_modifiers << klass
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
# Register a block to be called on output, for example a code formatter. Whatever is
|
|
@@ -73,11 +79,11 @@ module Nandi
|
|
|
73
79
|
def migration_directory(database_name = nil) = config(database_name).migration_directory
|
|
74
80
|
def output_directory(database_name = nil) = config(database_name).output_directory
|
|
75
81
|
def access_exclusive_lock_timeout(database_name = nil) = config(database_name).access_exclusive_lock_timeout
|
|
76
|
-
def
|
|
82
|
+
def access_exclusive_lock_timeout_max(database_name = nil) = config(database_name).access_exclusive_lock_timeout_max
|
|
77
83
|
def access_exclusive_statement_timeout(database_name = nil) = config(database_name).access_exclusive_statement_timeout
|
|
78
|
-
def
|
|
79
|
-
def
|
|
80
|
-
def
|
|
84
|
+
def access_exclusive_statement_timeout_max(database_name = nil) = config(database_name).access_exclusive_statement_timeout_max
|
|
85
|
+
def concurrent_lock_timeout_min(database_name = nil) = config(database_name).concurrent_lock_timeout_min
|
|
86
|
+
def concurrent_statement_timeout_min(database_name = nil) = config(database_name).concurrent_statement_timeout_min
|
|
81
87
|
def concurrent_lock_timeout(database_name = nil) = config(database_name).concurrent_lock_timeout
|
|
82
88
|
def concurrent_statement_timeout(database_name = nil) = config(database_name).concurrent_statement_timeout
|
|
83
89
|
# rubocop:enable Layout/LineLength
|
|
@@ -86,11 +92,11 @@ module Nandi
|
|
|
86
92
|
delegate :migration_directory=,
|
|
87
93
|
:output_directory=,
|
|
88
94
|
:access_exclusive_lock_timeout=,
|
|
89
|
-
:
|
|
95
|
+
:access_exclusive_lock_timeout_max=,
|
|
90
96
|
:access_exclusive_statement_timeout=,
|
|
91
|
-
:
|
|
92
|
-
:
|
|
93
|
-
:
|
|
97
|
+
:access_exclusive_statement_timeout_max=,
|
|
98
|
+
:concurrent_lock_timeout_min=,
|
|
99
|
+
:concurrent_statement_timeout_min=,
|
|
94
100
|
:concurrent_lock_timeout=,
|
|
95
101
|
:concurrent_statement_timeout=,
|
|
96
102
|
to: :default
|
|
@@ -5,24 +5,29 @@ require "active_support/inflector"
|
|
|
5
5
|
module Nandi
|
|
6
6
|
module Instructions
|
|
7
7
|
class AddForeignKey
|
|
8
|
-
attr_reader :table, :target
|
|
8
|
+
attr_reader :table, :target, :validate
|
|
9
9
|
|
|
10
10
|
def initialize(table:, target:, name: nil, **extra_args)
|
|
11
11
|
@table = table
|
|
12
12
|
@target = target
|
|
13
13
|
@extra_args = extra_args
|
|
14
14
|
@name = name
|
|
15
|
+
@validate = false
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def procedure
|
|
18
19
|
:add_foreign_key
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
def validate!
|
|
23
|
+
@validate = true
|
|
24
|
+
end
|
|
25
|
+
|
|
21
26
|
def extra_args
|
|
22
27
|
{
|
|
23
28
|
**@extra_args,
|
|
24
29
|
name: name,
|
|
25
|
-
validate:
|
|
30
|
+
validate: validate,
|
|
26
31
|
}.compact
|
|
27
32
|
end
|
|
28
33
|
|
data/lib/nandi/migration.rb
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nandi
|
|
4
|
+
module MigrationModifiers
|
|
5
|
+
class CreateTableValidatesFks < Base
|
|
6
|
+
def self.up(instructions)
|
|
7
|
+
new_tables = instructions.
|
|
8
|
+
select { |i| i.procedure == :create_table }.
|
|
9
|
+
to_set { |i| i.table.to_sym }
|
|
10
|
+
|
|
11
|
+
return if new_tables.empty?
|
|
12
|
+
|
|
13
|
+
instructions.
|
|
14
|
+
grep(Instructions::AddForeignKey).
|
|
15
|
+
select { |i| new_tables.include?(i.table.to_sym) }.
|
|
16
|
+
each(&:validate!)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/nandi/multi_database.rb
CHANGED
|
@@ -9,11 +9,12 @@ module Nandi
|
|
|
9
9
|
DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT = 1_500
|
|
10
10
|
DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT = 5_000
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT_MAX =
|
|
13
13
|
DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT
|
|
14
|
-
|
|
14
|
+
DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT_MAX =
|
|
15
15
|
DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT
|
|
16
|
-
|
|
16
|
+
DEFAULT_CONCURRENT_LOCK_TIMEOUT_MIN = 10_000
|
|
17
|
+
DEFAULT_CONCURRENT_STATEMENT_TIMEOUT_MIN = 30_000
|
|
17
18
|
|
|
18
19
|
DEFAULT_MIGRATION_DIRECTORY = "db/safe_migrations"
|
|
19
20
|
DEFAULT_OUTPUT_DIRECTORY = "db/migrate"
|
|
@@ -33,22 +34,22 @@ module Nandi
|
|
|
33
34
|
# The maximum lock timeout for migrations that take an ACCESS EXCLUSIVE
|
|
34
35
|
# lock and therefore block all reads and writes. Default: 5,000ms.
|
|
35
36
|
# @return [Integer]
|
|
36
|
-
attr_accessor :
|
|
37
|
+
attr_accessor :access_exclusive_statement_timeout_max
|
|
37
38
|
|
|
38
39
|
# The maximum statement timeout for migrations that take an ACCESS
|
|
39
40
|
# EXCLUSIVE lock and therefore block all reads and writes. Default: 1500ms.
|
|
40
41
|
# @return [Integer]
|
|
41
|
-
attr_accessor :
|
|
42
|
+
attr_accessor :access_exclusive_lock_timeout_max
|
|
42
43
|
|
|
43
44
|
# The minimum statement timeout for migrations that take place concurrently.
|
|
44
45
|
# Default: 3,600,000ms (ie, 3 hours).
|
|
45
46
|
# @return [Integer]
|
|
46
|
-
attr_accessor :
|
|
47
|
+
attr_accessor :concurrent_statement_timeout_min
|
|
47
48
|
|
|
48
49
|
# The minimum lock timeout for migrations that take place concurrently.
|
|
49
50
|
# Default: 3,600,000ms (ie, 3 hours).
|
|
50
51
|
# @return [Integer]
|
|
51
|
-
attr_accessor :
|
|
52
|
+
attr_accessor :concurrent_lock_timeout_min
|
|
52
53
|
|
|
53
54
|
# The default lock timeout for migrations that take place concurrently
|
|
54
55
|
# (eg. add_index, remove_index). When set, concurrent migrations will use
|
|
@@ -71,6 +72,13 @@ module Nandi
|
|
|
71
72
|
attr_accessor :migration_directory,
|
|
72
73
|
:lockfile_name
|
|
73
74
|
|
|
75
|
+
RENAMED_KEYS = {
|
|
76
|
+
access_exclusive_lock_timeout_limit: :access_exclusive_lock_timeout_max,
|
|
77
|
+
access_exclusive_statement_timeout_limit: :access_exclusive_statement_timeout_max,
|
|
78
|
+
concurrent_lock_timeout_limit: :concurrent_lock_timeout_min,
|
|
79
|
+
concurrent_statement_timeout_limit: :concurrent_statement_timeout_min,
|
|
80
|
+
}.freeze
|
|
81
|
+
|
|
74
82
|
def initialize(name:, config:)
|
|
75
83
|
@name = name
|
|
76
84
|
@raw_config = config
|
|
@@ -81,24 +89,36 @@ module Nandi
|
|
|
81
89
|
@output_directory = config[:output_directory] || "db/#{path_prefix(name, default)}migrate"
|
|
82
90
|
@lockfile_name = config[:lockfile_name] || ".#{path_prefix(name, default)}nandilock.yml"
|
|
83
91
|
|
|
84
|
-
timeout_limits(config)
|
|
92
|
+
timeout_limits(normalize_config(config))
|
|
85
93
|
end
|
|
86
94
|
|
|
87
95
|
private
|
|
88
96
|
|
|
97
|
+
def normalize_config(config)
|
|
98
|
+
RENAMED_KEYS.each_with_object(config.dup) do |(old_key, new_key), normalized|
|
|
99
|
+
next unless normalized.key?(old_key)
|
|
100
|
+
|
|
101
|
+
Nandi::DEPRECATOR.warn(
|
|
102
|
+
"#{old_key} is deprecated and will be removed in a future version. " \
|
|
103
|
+
"Use #{new_key} instead.",
|
|
104
|
+
)
|
|
105
|
+
normalized[new_key] ||= normalized.delete(old_key)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
89
109
|
def timeout_limits(config)
|
|
90
110
|
@access_exclusive_lock_timeout =
|
|
91
111
|
config[:access_exclusive_lock_timeout] || DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT
|
|
92
112
|
@access_exclusive_statement_timeout =
|
|
93
113
|
config[:access_exclusive_statement_timeout] || DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT
|
|
94
|
-
@
|
|
95
|
-
config[:
|
|
96
|
-
@
|
|
97
|
-
config[:
|
|
98
|
-
@
|
|
99
|
-
config[:
|
|
100
|
-
@
|
|
101
|
-
config[:
|
|
114
|
+
@access_exclusive_lock_timeout_max =
|
|
115
|
+
config[:access_exclusive_lock_timeout_max] || DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT_MAX
|
|
116
|
+
@access_exclusive_statement_timeout_max =
|
|
117
|
+
config[:access_exclusive_statement_timeout_max] || DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT_MAX
|
|
118
|
+
@concurrent_lock_timeout_min =
|
|
119
|
+
config[:concurrent_lock_timeout_min] || DEFAULT_CONCURRENT_LOCK_TIMEOUT_MIN
|
|
120
|
+
@concurrent_statement_timeout_min =
|
|
121
|
+
config[:concurrent_statement_timeout_min] || DEFAULT_CONCURRENT_STATEMENT_TIMEOUT_MIN
|
|
102
122
|
@concurrent_lock_timeout = config[:concurrent_lock_timeout]
|
|
103
123
|
@concurrent_statement_timeout = config[:concurrent_statement_timeout]
|
|
104
124
|
end
|
|
@@ -43,11 +43,11 @@ module Nandi
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def statement_timeout_maximum
|
|
46
|
-
Nandi.config.
|
|
46
|
+
Nandi.config.access_exclusive_statement_timeout_max
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def lock_timeout_maximum
|
|
50
|
-
Nandi.config.
|
|
50
|
+
Nandi.config.access_exclusive_lock_timeout_max
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
end
|
|
@@ -53,11 +53,11 @@ module Nandi
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def minimum_lock_timeout
|
|
56
|
-
Nandi.config.
|
|
56
|
+
Nandi.config.concurrent_lock_timeout_min
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def minimum_statement_timeout
|
|
60
|
-
Nandi.config.
|
|
60
|
+
Nandi.config.concurrent_statement_timeout_min
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
end
|
data/lib/nandi/validator.rb
CHANGED
|
@@ -74,13 +74,13 @@ module Nandi
|
|
|
74
74
|
def statement_timeout_is_within_acceptable_bounds
|
|
75
75
|
migration.strictest_lock != Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE ||
|
|
76
76
|
migration.statement_timeout <=
|
|
77
|
-
Nandi.config.
|
|
77
|
+
Nandi.config.access_exclusive_statement_timeout_max
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def lock_timeout_is_within_acceptable_bounds
|
|
81
81
|
migration.strictest_lock != Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE ||
|
|
82
82
|
migration.lock_timeout <=
|
|
83
|
-
Nandi.config.
|
|
83
|
+
Nandi.config.access_exclusive_lock_timeout_max
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def each_instruction_validation
|
data/lib/nandi/version.rb
CHANGED
data/lib/nandi.rb
CHANGED
|
@@ -4,8 +4,11 @@ require "nandi/config"
|
|
|
4
4
|
require "nandi/renderers"
|
|
5
5
|
require "nandi/compiled_migration"
|
|
6
6
|
require "active_support/core_ext/string/inflections"
|
|
7
|
+
require "active_support/deprecation"
|
|
7
8
|
|
|
8
9
|
module Nandi
|
|
10
|
+
DEPRECATOR = ActiveSupport::Deprecation.new("4.0", "Nandi")
|
|
11
|
+
|
|
9
12
|
class Error < StandardError; end
|
|
10
13
|
|
|
11
14
|
class << self
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nandi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GoCardless Engineering
|
|
@@ -120,6 +120,9 @@ files:
|
|
|
120
120
|
- lib/nandi/instructions/validate_constraint.rb
|
|
121
121
|
- lib/nandi/lockfile.rb
|
|
122
122
|
- lib/nandi/migration.rb
|
|
123
|
+
- lib/nandi/migration_modifiers.rb
|
|
124
|
+
- lib/nandi/migration_modifiers/base.rb
|
|
125
|
+
- lib/nandi/migration_modifiers/create_table_validates_fks.rb
|
|
123
126
|
- lib/nandi/migration_violations.rb
|
|
124
127
|
- lib/nandi/multi_database.rb
|
|
125
128
|
- lib/nandi/multi_db_generator.rb
|
|
@@ -177,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
177
180
|
- !ruby/object:Gem::Version
|
|
178
181
|
version: '0'
|
|
179
182
|
requirements: []
|
|
180
|
-
rubygems_version: 4.0.
|
|
183
|
+
rubygems_version: 4.0.10
|
|
181
184
|
specification_version: 4
|
|
182
185
|
summary: Fear-free migrations for PostgreSQL
|
|
183
186
|
test_files: []
|