nandi 0.9.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 +7 -0
- data/README.md +477 -0
- data/Rakefile +8 -0
- data/exe/nandi-enforce +36 -0
- data/lib/generators/nandi/check_constraint/USAGE +19 -0
- data/lib/generators/nandi/check_constraint/check_constraint_generator.rb +52 -0
- data/lib/generators/nandi/check_constraint/templates/add_check_constraint.rb +15 -0
- data/lib/generators/nandi/check_constraint/templates/validate_check_constraint.rb +9 -0
- data/lib/generators/nandi/compile/USAGE +19 -0
- data/lib/generators/nandi/compile/compile_generator.rb +62 -0
- data/lib/generators/nandi/foreign_key/USAGE +47 -0
- data/lib/generators/nandi/foreign_key/foreign_key_generator.rb +91 -0
- data/lib/generators/nandi/foreign_key/templates/add_foreign_key.rb +13 -0
- data/lib/generators/nandi/foreign_key/templates/add_reference.rb +11 -0
- data/lib/generators/nandi/foreign_key/templates/validate_foreign_key.rb +9 -0
- data/lib/generators/nandi/migration/USAGE +9 -0
- data/lib/generators/nandi/migration/migration_generator.rb +24 -0
- data/lib/generators/nandi/migration/templates/migration.rb +13 -0
- data/lib/generators/nandi/not_null_check/USAGE +19 -0
- data/lib/generators/nandi/not_null_check/not_null_check_generator.rb +56 -0
- data/lib/generators/nandi/not_null_check/templates/add_not_null_check.rb +11 -0
- data/lib/generators/nandi/not_null_check/templates/validate_not_null_check.rb +9 -0
- data/lib/nandi.rb +35 -0
- data/lib/nandi/compiled_migration.rb +86 -0
- data/lib/nandi/config.rb +126 -0
- data/lib/nandi/file_diff.rb +32 -0
- data/lib/nandi/file_matcher.rb +72 -0
- data/lib/nandi/formatting.rb +79 -0
- data/lib/nandi/instructions.rb +19 -0
- data/lib/nandi/instructions/add_check_constraint.rb +23 -0
- data/lib/nandi/instructions/add_column.rb +24 -0
- data/lib/nandi/instructions/add_foreign_key.rb +40 -0
- data/lib/nandi/instructions/add_index.rb +50 -0
- data/lib/nandi/instructions/change_column_default.rb +23 -0
- data/lib/nandi/instructions/create_table.rb +83 -0
- data/lib/nandi/instructions/drop_constraint.rb +22 -0
- data/lib/nandi/instructions/drop_table.rb +21 -0
- data/lib/nandi/instructions/irreversible_migration.rb +15 -0
- data/lib/nandi/instructions/remove_column.rb +23 -0
- data/lib/nandi/instructions/remove_index.rb +41 -0
- data/lib/nandi/instructions/remove_not_null_constraint.rb +22 -0
- data/lib/nandi/instructions/validate_constraint.rb +22 -0
- data/lib/nandi/lockfile.rb +58 -0
- data/lib/nandi/migration.rb +363 -0
- data/lib/nandi/renderers.rb +7 -0
- data/lib/nandi/renderers/active_record.rb +13 -0
- data/lib/nandi/renderers/active_record/generate.rb +59 -0
- data/lib/nandi/renderers/active_record/instructions.rb +134 -0
- data/lib/nandi/safe_migration_enforcer.rb +143 -0
- data/lib/nandi/timeout_policies.rb +38 -0
- data/lib/nandi/timeout_policies/access_exclusive.rb +54 -0
- data/lib/nandi/timeout_policies/concurrent.rb +64 -0
- data/lib/nandi/validation.rb +10 -0
- data/lib/nandi/validation/add_column_validator.rb +43 -0
- data/lib/nandi/validation/each_validator.rb +32 -0
- data/lib/nandi/validation/failure_helpers.rb +35 -0
- data/lib/nandi/validation/remove_index_validator.rb +30 -0
- data/lib/nandi/validation/result.rb +30 -0
- data/lib/nandi/validation/timeout_validator.rb +37 -0
- data/lib/nandi/validator.rb +102 -0
- data/lib/nandi/version.rb +5 -0
- data/lib/templates/nandi/renderers/active_record/generate/show.rb.erb +27 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_check_constraint/show.rb.erb +7 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_column/show.rb.erb +6 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_foreign_key/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_index/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/change_column_default/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/create_table/show.rb.erb +8 -0
- data/lib/templates/nandi/renderers/active_record/instructions/drop_constraint/show.rb.erb +4 -0
- data/lib/templates/nandi/renderers/active_record/instructions/drop_table/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/irreversible_migration/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_column/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_index/show.rb.erb +4 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_not_null_constraint/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/validate_constraint/show.rb.erb +3 -0
- metadata +320 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi"
|
4
|
+
require "nandi/validation"
|
5
|
+
require "nandi/validation/failure_helpers"
|
6
|
+
|
7
|
+
module Nandi
|
8
|
+
module TimeoutPolicies
|
9
|
+
class Concurrent
|
10
|
+
include Nandi::Validation::FailureHelpers
|
11
|
+
|
12
|
+
def self.validate(migration)
|
13
|
+
new(migration).validate
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(migration)
|
17
|
+
@migration = migration
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate
|
21
|
+
collect_errors(
|
22
|
+
validate_statement_timeout,
|
23
|
+
validate_lock_timeout,
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_accessor :migration
|
30
|
+
|
31
|
+
def validate_statement_timeout
|
32
|
+
assert(
|
33
|
+
migration.disable_statement_timeout? || statement_timeout_high_enough,
|
34
|
+
"statement timeout for concurrent operations "\
|
35
|
+
"must be at least #{minimum_statement_timeout}",
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_lock_timeout
|
40
|
+
assert(
|
41
|
+
migration.disable_lock_timeout? || lock_timeout_high_enough,
|
42
|
+
"lock timeout for concurrent operations "\
|
43
|
+
"must be at least #{minimum_lock_timeout}",
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def statement_timeout_high_enough
|
48
|
+
migration.statement_timeout >= minimum_statement_timeout
|
49
|
+
end
|
50
|
+
|
51
|
+
def lock_timeout_high_enough
|
52
|
+
migration.lock_timeout >= minimum_lock_timeout
|
53
|
+
end
|
54
|
+
|
55
|
+
def minimum_lock_timeout
|
56
|
+
Nandi.config.concurrent_lock_timeout_limit
|
57
|
+
end
|
58
|
+
|
59
|
+
def minimum_statement_timeout
|
60
|
+
Nandi.config.concurrent_statement_timeout_limit
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/validation/add_column_validator"
|
4
|
+
require "nandi/validation/remove_index_validator"
|
5
|
+
require "nandi/validation/each_validator"
|
6
|
+
require "nandi/validation/result"
|
7
|
+
require "nandi/validation/failure_helpers"
|
8
|
+
require "nandi/validation/timeout_validator"
|
9
|
+
|
10
|
+
module Validation; end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/validation/failure_helpers"
|
4
|
+
|
5
|
+
module Nandi
|
6
|
+
module Validation
|
7
|
+
class AddColumnValidator
|
8
|
+
include Nandi::Validation::FailureHelpers
|
9
|
+
|
10
|
+
def self.call(instruction)
|
11
|
+
new(instruction).call
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(instruction)
|
15
|
+
@instruction = instruction
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
collect_errors(
|
20
|
+
assert(nullable? || default_value?,
|
21
|
+
"add_column: non-null column lacks default"),
|
22
|
+
assert(!unique?, "add_column: column is unique"),
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :instruction
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def default_value?
|
31
|
+
!instruction.extra_args[:default].nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
def nullable?
|
35
|
+
instruction.extra_args.fetch(:null, true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def unique?
|
39
|
+
instruction.extra_args.fetch(:unique, false)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/validation/failure_helpers"
|
4
|
+
|
5
|
+
module Nandi
|
6
|
+
module Validation
|
7
|
+
class EachValidator
|
8
|
+
include Nandi::Validation::FailureHelpers
|
9
|
+
|
10
|
+
def self.call(instruction)
|
11
|
+
new(instruction).call
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(instruction)
|
15
|
+
@instruction = instruction
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
case instruction.procedure
|
20
|
+
when :remove_index
|
21
|
+
RemoveIndexValidator.call(instruction)
|
22
|
+
when :add_column
|
23
|
+
AddColumnValidator.call(instruction)
|
24
|
+
else
|
25
|
+
success
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :instruction
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads/result"
|
4
|
+
|
5
|
+
module Nandi
|
6
|
+
module Validation
|
7
|
+
module FailureHelpers
|
8
|
+
def collect_errors(new, old)
|
9
|
+
return success if new.success? && old.success?
|
10
|
+
|
11
|
+
if old.failure?
|
12
|
+
failure(Array(old.failure) + Array(new.failure))
|
13
|
+
else
|
14
|
+
failure(Array(new.failure))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def success
|
19
|
+
Dry::Monads::Result::Success.new(nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
def failure(value)
|
23
|
+
Dry::Monads::Result::Failure.new(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert(condition, message)
|
27
|
+
if condition
|
28
|
+
success
|
29
|
+
else
|
30
|
+
failure(message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/validation/failure_helpers"
|
4
|
+
|
5
|
+
module Nandi
|
6
|
+
module Validation
|
7
|
+
class RemoveIndexValidator
|
8
|
+
include Nandi::Validation::FailureHelpers
|
9
|
+
|
10
|
+
def self.call(instruction)
|
11
|
+
new(instruction).call
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(instruction)
|
15
|
+
@instruction = instruction
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
opts = instruction.extra_args
|
20
|
+
|
21
|
+
assert(
|
22
|
+
opts.key?(:name) || opts.key?(:column),
|
23
|
+
"remove_index: requires a `name` or `column` argument",
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :instruction
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/validation/failure_helpers"
|
4
|
+
module Nandi
|
5
|
+
module Validation
|
6
|
+
class Result
|
7
|
+
include Nandi::Validation::FailureHelpers
|
8
|
+
|
9
|
+
attr_reader :errors
|
10
|
+
|
11
|
+
def initialize(instruction = nil)
|
12
|
+
@instruction = instruction
|
13
|
+
@errors = success
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
@errors.success?
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(error)
|
21
|
+
@errors = collect_errors(error, @errors)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def error_list
|
26
|
+
@errors.failure.join("\n")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/validation/failure_helpers"
|
4
|
+
|
5
|
+
module Nandi
|
6
|
+
module Validation
|
7
|
+
class TimeoutValidator
|
8
|
+
include Nandi::Validation::FailureHelpers
|
9
|
+
|
10
|
+
def self.call(migration)
|
11
|
+
new(migration).call
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(migration)
|
15
|
+
@migration = migration
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
timeout_policies.inject(success) do |result, policy|
|
20
|
+
collect_errors(policy.validate(migration), result)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def timeout_policies
|
27
|
+
instructions.map(&Nandi::TimeoutPolicies.method(:policy_for)).uniq
|
28
|
+
end
|
29
|
+
|
30
|
+
def instructions
|
31
|
+
[*migration.up_instructions, *migration.down_instructions]
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :migration
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/monads"
|
4
|
+
require "nandi/validation"
|
5
|
+
require "nandi/timeout_policies"
|
6
|
+
|
7
|
+
module Nandi
|
8
|
+
class Validator
|
9
|
+
include Nandi::Validation::FailureHelpers
|
10
|
+
|
11
|
+
class InstructionValidator
|
12
|
+
def self.call(instruction)
|
13
|
+
new(instruction).call
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(instruction)
|
17
|
+
@instruction = instruction
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :instruction
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.call(migration)
|
28
|
+
new(migration).call
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(migration)
|
32
|
+
@migration = migration
|
33
|
+
end
|
34
|
+
|
35
|
+
def call
|
36
|
+
migration_invariants_respected << each_instruction_validation
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def migration_invariants_respected
|
42
|
+
Validation::Result.new.tap do |result|
|
43
|
+
result << assert(
|
44
|
+
at_most_one_object_modified,
|
45
|
+
"modifying more than one table per migration",
|
46
|
+
)
|
47
|
+
|
48
|
+
result << assert(
|
49
|
+
new_indexes_are_separated_from_other_migrations,
|
50
|
+
"creating more than one index per migration",
|
51
|
+
)
|
52
|
+
|
53
|
+
result << validate_timeouts
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def at_most_one_object_modified
|
58
|
+
[migration.up_instructions, migration.down_instructions].all? do |instructions|
|
59
|
+
affected_tables = instructions.map do |instruction|
|
60
|
+
instruction.respond_to?(:table) && instruction.table.to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
affected_tables.uniq.count <= 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def new_indexes_are_separated_from_other_migrations
|
68
|
+
[migration.up_instructions, migration.down_instructions].map do |instructions|
|
69
|
+
instructions.none? { |i| i.procedure == :add_index } ||
|
70
|
+
instructions.count == 1
|
71
|
+
end.all?
|
72
|
+
end
|
73
|
+
|
74
|
+
def statement_timeout_is_within_acceptable_bounds
|
75
|
+
migration.strictest_lock != Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE ||
|
76
|
+
migration.statement_timeout <=
|
77
|
+
Nandi.config.access_exclusive_statement_timeout_limit
|
78
|
+
end
|
79
|
+
|
80
|
+
def lock_timeout_is_within_acceptable_bounds
|
81
|
+
migration.strictest_lock != Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE ||
|
82
|
+
migration.lock_timeout <=
|
83
|
+
Nandi.config.access_exclusive_lock_timeout_limit
|
84
|
+
end
|
85
|
+
|
86
|
+
def each_instruction_validation
|
87
|
+
instructions.inject(success) do |result, instruction|
|
88
|
+
collect_errors(Validation::EachValidator.call(instruction), result)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_timeouts
|
93
|
+
Nandi::Validation::TimeoutValidator.call(migration)
|
94
|
+
end
|
95
|
+
|
96
|
+
def instructions
|
97
|
+
[*migration.up_instructions, *migration.down_instructions]
|
98
|
+
end
|
99
|
+
|
100
|
+
attr_reader :migration
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class <%= name %> < ActiveRecord::Migration[<%= activerecord_version %>]
|
2
|
+
<%= mixins.map { |m| "include #{m.name}"}.join("\n") %>
|
3
|
+
<% if disable_lock_timeout? %>
|
4
|
+
disable_lock_timeout!
|
5
|
+
<% else %>
|
6
|
+
set_lock_timeout(<%= lock_timeout %>)
|
7
|
+
<% end %>
|
8
|
+
<% if disable_statement_timeout? %>
|
9
|
+
disable_statement_timeout!
|
10
|
+
<% else %>
|
11
|
+
set_statement_timeout(<%= statement_timeout %>)
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<% if should_disable_ddl_transaction? %>disable_ddl_transaction!<% end %>
|
15
|
+
def up
|
16
|
+
<% up_instructions.each do |i| %>
|
17
|
+
<%= render_partial(i) %>
|
18
|
+
<% end %>
|
19
|
+
end
|
20
|
+
<% if down_instructions&.any? %>
|
21
|
+
def down
|
22
|
+
<% down_instructions.each do |i| %>
|
23
|
+
<%= render_partial(i) %>
|
24
|
+
<% end %>
|
25
|
+
end
|
26
|
+
<% end %>
|
27
|
+
end
|