nandi 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/Rakefile
ADDED
data/exe/nandi-enforce
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require "bundler"
|
6
|
+
Bundler.setup(:default)
|
7
|
+
|
8
|
+
require "optparse"
|
9
|
+
|
10
|
+
require_relative "../lib/nandi"
|
11
|
+
require_relative "../lib/nandi/safe_migration_enforcer"
|
12
|
+
|
13
|
+
opts = {}
|
14
|
+
OptionParser.new do |o|
|
15
|
+
o.on("--safe-migration-dir DIR", "directory containing Nandi migrations") do |v|
|
16
|
+
opts[:safe_migration_dir] = v
|
17
|
+
end
|
18
|
+
o.on("--ar-migration-dir DIR", "directory containing ActiveRecord migrations") do |v|
|
19
|
+
opts[:ar_migration_dir] = v
|
20
|
+
end
|
21
|
+
o.on("--require PATH", "file to require before execution") do |v|
|
22
|
+
opts[:require_path] = v
|
23
|
+
end
|
24
|
+
o.on("--files FILE_SPEC",
|
25
|
+
"Files to compile for check - eg, all, git-diff, 201901010101") do |v|
|
26
|
+
opts[:files] = v
|
27
|
+
end
|
28
|
+
o.on("-h", "--help") do
|
29
|
+
puts o
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end.parse!
|
33
|
+
|
34
|
+
enforcer = Nandi::SafeMigrationEnforcer.new(opts)
|
35
|
+
|
36
|
+
enforcer.run
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Description:
|
2
|
+
Generates two new database migrations which will safely add an arbitrary
|
3
|
+
check constraint and validate it separately.
|
4
|
+
|
5
|
+
Example:
|
6
|
+
rails generate nandi:check_constraint foos baz_or_quux_not_null
|
7
|
+
|
8
|
+
This will create:
|
9
|
+
db/safe_migrations/20190424123727_add_check_constraint_baz_or_quux_not_null_on_foos.rb
|
10
|
+
db/safe_migrations/20190424123728_validate_check_constraint_baz_or_quux_not_null_on_foos.rb
|
11
|
+
|
12
|
+
Example:
|
13
|
+
rails generate nandi:check_constraint foos baz_or_quux_not_null --validation-timeout 20000
|
14
|
+
|
15
|
+
This will create:
|
16
|
+
db/safe_migrations/20190424123727_add_check_constraint_baz_or_quux_not_null_on_foos.rb
|
17
|
+
db/safe_migrations/20190424123728_validate_check_constraint_baz_or_quux_not_null_on_foos.rb
|
18
|
+
|
19
|
+
The statement timeout in the second migration will be set to 20,000ms.
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "nandi/formatting"
|
5
|
+
|
6
|
+
module Nandi
|
7
|
+
class CheckConstraintGenerator < Rails::Generators::Base
|
8
|
+
include Nandi::Formatting
|
9
|
+
|
10
|
+
argument :table, type: :string
|
11
|
+
argument :name, type: :string
|
12
|
+
class_option :validation_timeout, type: :numeric, default: 15 * 60 * 1000
|
13
|
+
|
14
|
+
source_root File.expand_path("templates", __dir__)
|
15
|
+
|
16
|
+
attr_reader :add_check_constraint_name, :validate_check_constraint_name
|
17
|
+
|
18
|
+
def add_check_constraint
|
19
|
+
self.table = table.to_sym
|
20
|
+
self.name = name.to_sym
|
21
|
+
|
22
|
+
@add_check_constraint_name = "add_check_constraint_#{name}_on_#{table}"
|
23
|
+
|
24
|
+
template(
|
25
|
+
"add_check_constraint.rb",
|
26
|
+
"#{base_path}/#{timestamp}_#{add_check_constraint_name}.rb",
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_check_constraint
|
31
|
+
self.table = table.to_sym
|
32
|
+
self.name = name.to_sym
|
33
|
+
|
34
|
+
@validate_check_constraint_name = "validate_check_constraint_#{name}_on_#{table}"
|
35
|
+
|
36
|
+
template(
|
37
|
+
"validate_check_constraint.rb",
|
38
|
+
"#{base_path}/#{timestamp(1)}_#{validate_check_constraint_name}.rb",
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def base_path
|
45
|
+
Nandi.config.migration_directory || "db/safe_migrations"
|
46
|
+
end
|
47
|
+
|
48
|
+
def timestamp(offset = 0)
|
49
|
+
(Time.now.utc + offset).strftime("%Y%m%d%H%M%S")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= add_check_constraint_name.camelize %> < Nandi::Migration
|
4
|
+
def up
|
5
|
+
add_check_constraint <%= format_value(table) %>,
|
6
|
+
<%= format_value(name) %>,
|
7
|
+
<<~SQL
|
8
|
+
-- foo IS NOT NULL OR bar IS NOT NULL
|
9
|
+
SQL
|
10
|
+
end
|
11
|
+
|
12
|
+
def down
|
13
|
+
drop_constraint <%= format_value(table) %>, <%= format_value(name) %>
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Description:
|
2
|
+
Takes all files from the db/nandi directory and turns
|
3
|
+
them into ActiveRecordmigration files.
|
4
|
+
|
5
|
+
Examples:
|
6
|
+
rails generate nandi:compile
|
7
|
+
compiles all migrations that have changed since last git commit
|
8
|
+
|
9
|
+
rails generate nandi:compile --files all
|
10
|
+
compiles all migrations ever
|
11
|
+
|
12
|
+
rails generate nandi:compile --files 20190101010101
|
13
|
+
compiles specific migration
|
14
|
+
|
15
|
+
rails generate nandi:compile --files 20190101
|
16
|
+
compiles all migrations from January 1st 2019
|
17
|
+
|
18
|
+
rails generate nandi:compile --files >=20190101
|
19
|
+
compiles all migrations from January 1st 2019 and after
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "nandi"
|
5
|
+
require "nandi/migration"
|
6
|
+
require "nandi/file_matcher"
|
7
|
+
require "nandi/lockfile"
|
8
|
+
|
9
|
+
module Nandi
|
10
|
+
class CompileGenerator < Rails::Generators::Base
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
12
|
+
|
13
|
+
class_option :files,
|
14
|
+
type: :string,
|
15
|
+
default: Nandi.config.compile_files,
|
16
|
+
desc: <<-DESC
|
17
|
+
Files to compile. May be one of the following:
|
18
|
+
-- 'all' compiles all files
|
19
|
+
-- 'git-diff' only changed
|
20
|
+
-- a full or partial version timestamp, eg '20190101010101', '20190101'
|
21
|
+
-- a timestamp range , eg '>=20190101010101'
|
22
|
+
DESC
|
23
|
+
|
24
|
+
def compile_migration_files
|
25
|
+
Nandi.compile(files: files) do |results|
|
26
|
+
results.each do |result|
|
27
|
+
Nandi::Lockfile.add(
|
28
|
+
file_name: result.file_name,
|
29
|
+
source_digest: result.source_digest,
|
30
|
+
compiled_digest: result.compiled_digest,
|
31
|
+
)
|
32
|
+
|
33
|
+
unless result.migration_unchanged?
|
34
|
+
create_file result.output_path, result.body, force: true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Nandi::Lockfile.persist!
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def safe_migrations_dir
|
45
|
+
if Nandi.config.migration_directory.nil?
|
46
|
+
Rails.root.join("db", "safe_migrations").to_s
|
47
|
+
else
|
48
|
+
File.expand_path(Nandi.config.migration_directory)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def output_path
|
53
|
+
Nandi.config.output_directory || "db/migrate"
|
54
|
+
end
|
55
|
+
|
56
|
+
def files
|
57
|
+
safe_migration_files = Dir.chdir(safe_migrations_dir) { Dir["*.rb"] }
|
58
|
+
FileMatcher.call(files: safe_migration_files, spec: options["files"]).
|
59
|
+
map { |file| File.join(safe_migrations_dir, file) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
Description:
|
2
|
+
Generates two new database migrations which will safely add a foreign key
|
3
|
+
constraint and validate it separately.
|
4
|
+
|
5
|
+
Example:
|
6
|
+
rails generate nandi:foreign_key foos bars
|
7
|
+
|
8
|
+
This will create:
|
9
|
+
db/safe_migrations/20190424123727_add_reference_on_bars_to_foos.rb
|
10
|
+
db/safe_migrations/20190424123728_add_foreign_key_on_bars_to_foos.rb
|
11
|
+
db/safe_migrations/20190424123729_validate_foreign_key_on_bars_to_foos.rb
|
12
|
+
|
13
|
+
These migrations will create a new column on the table foos called bar_id, a
|
14
|
+
new foreign key constraint on that column against the id column of bars, and
|
15
|
+
validate that constraint separately.
|
16
|
+
|
17
|
+
Example:
|
18
|
+
rails generate nandi:foreign_key foos bars --type text
|
19
|
+
|
20
|
+
This will create:
|
21
|
+
db/safe_migrations/20190424123727_add_reference_on_bars_to_foos.rb
|
22
|
+
db/safe_migrations/20190424123728_add_foreign_key_on_bars_to_foos.rb
|
23
|
+
db/safe_migrations/20190424123729_validate_foreign_key_on_bars_to_foos.rb
|
24
|
+
|
25
|
+
These migrations will create a new text column on the table foos called bar_id,
|
26
|
+
a new foreign key constraint on that column against the id column of bars, and
|
27
|
+
validate that constraint separately.
|
28
|
+
|
29
|
+
Example:
|
30
|
+
rails generate nandi:foreign_key foos bars --no-create-column
|
31
|
+
|
32
|
+
This will create:
|
33
|
+
db/safe_migrations/20190424123727_add_foreign_key_on_bars_to_foos.rb
|
34
|
+
db/safe_migrations/20190424123728_validate_foreign_key_on_bars_to_foos.rb
|
35
|
+
|
36
|
+
Assumes that there is a column on table foos called bar_id that points to bars.
|
37
|
+
Will create an FK constraint called foos_bars_fk.
|
38
|
+
|
39
|
+
Example:
|
40
|
+
rails generate nandi:foreign_key foos bars --column special_bar_id --name my_fk
|
41
|
+
|
42
|
+
This will create:
|
43
|
+
db/safe_migrations/20190424123727_add_foreign_key_on_bars_to_foos.rb
|
44
|
+
db/safe_migrations/20190424123728_validate_foreign_key_on_bars_to_foos.rb
|
45
|
+
|
46
|
+
Assumes that there is a column on table foos called special_bar_id that points to
|
47
|
+
bars. Will create an FK constraint called my_fk
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "nandi/formatting"
|
5
|
+
|
6
|
+
module Nandi
|
7
|
+
class ForeignKeyGenerator < Rails::Generators::Base
|
8
|
+
include Nandi::Formatting
|
9
|
+
|
10
|
+
argument :table, type: :string
|
11
|
+
argument :target, type: :string
|
12
|
+
class_option :name, type: :string
|
13
|
+
class_option :column, type: :string
|
14
|
+
class_option :type, type: :string, default: "bigint"
|
15
|
+
class_option :no_create_column, type: :boolean
|
16
|
+
class_option :validation_timeout, type: :numeric, default: 15 * 60 * 1000
|
17
|
+
|
18
|
+
source_root File.expand_path("templates", __dir__)
|
19
|
+
|
20
|
+
attr_reader :add_reference_name,
|
21
|
+
:add_foreign_key_name,
|
22
|
+
:validate_foreign_key_name
|
23
|
+
|
24
|
+
def add_reference
|
25
|
+
return if options["no_create_column"]
|
26
|
+
|
27
|
+
self.table = table.to_sym
|
28
|
+
|
29
|
+
@add_reference_name = "add_reference_on_#{table}_to_#{target}"
|
30
|
+
|
31
|
+
template(
|
32
|
+
"add_reference.rb",
|
33
|
+
"#{base_path}/#{timestamp}_#{add_reference_name}.rb",
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_foreign_key
|
38
|
+
self.table = table.to_sym
|
39
|
+
self.target = target.to_sym
|
40
|
+
|
41
|
+
@add_foreign_key_name = "add_foreign_key_on_#{table}_to_#{target}"
|
42
|
+
|
43
|
+
template(
|
44
|
+
"add_foreign_key.rb",
|
45
|
+
"#{base_path}/#{timestamp(1)}_#{add_foreign_key_name}.rb",
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_foreign_key
|
50
|
+
self.table = table.to_sym
|
51
|
+
self.target = target.to_sym
|
52
|
+
|
53
|
+
@validate_foreign_key_name = "validate_foreign_key_on_#{table}_to_#{target}"
|
54
|
+
|
55
|
+
template(
|
56
|
+
"validate_foreign_key.rb",
|
57
|
+
"#{base_path}/#{timestamp(2)}_#{validate_foreign_key_name}.rb",
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def type
|
64
|
+
options["type"].to_sym
|
65
|
+
end
|
66
|
+
|
67
|
+
def reference_name
|
68
|
+
"#{target.singularize}_id".to_sym
|
69
|
+
end
|
70
|
+
|
71
|
+
def base_path
|
72
|
+
Nandi.config.migration_directory || "db/safe_migrations"
|
73
|
+
end
|
74
|
+
|
75
|
+
def timestamp(offset = 0)
|
76
|
+
(Time.now.utc + offset).strftime("%Y%m%d%H%M%S")
|
77
|
+
end
|
78
|
+
|
79
|
+
def name
|
80
|
+
options["name"]&.to_sym || :"#{@table}_#{@target}_fk"
|
81
|
+
end
|
82
|
+
|
83
|
+
def column
|
84
|
+
options["column"]&.to_sym
|
85
|
+
end
|
86
|
+
|
87
|
+
def any_options?
|
88
|
+
options["name"] || options["column"]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= add_foreign_key_name.camelize %> < Nandi::Migration
|
4
|
+
def up
|
5
|
+
add_foreign_key <%= format_value(table) %>, <%= format_value(target) %><% if any_options? %>,
|
6
|
+
<% if column %>column: <%= format_value(column) %><% end %><% if column && name %>,<% end %>
|
7
|
+
<% if name %>name: <%= format_value(name) %><% end %> <% end %>
|
8
|
+
end
|
9
|
+
|
10
|
+
def down
|
11
|
+
drop_constraint <%= format_value(table) %>, <%= format_value(name) %>
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= add_reference_name.camelize %> < Nandi::Migration
|
4
|
+
def up
|
5
|
+
add_column <%= format_value(table) %>, <%= format_value(reference_name) %>, <%= format_value(type) %>
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
remove_column <%= format_value(table) %>, <%= format_value(reference_name) %>
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
|
5
|
+
module Nandi
|
6
|
+
class MigrationGenerator < Rails::Generators::NamedBase
|
7
|
+
source_root File.expand_path("templates", __dir__)
|
8
|
+
|
9
|
+
def create_migration_file
|
10
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
11
|
+
|
12
|
+
template(
|
13
|
+
"migration.rb",
|
14
|
+
"#{base_path}/#{timestamp}_#{file_name.underscore}.rb",
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def base_path
|
21
|
+
Nandi.config.migration_directory || "db/safe_migrations"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|