nandi 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +477 -0
  3. data/Rakefile +8 -0
  4. data/exe/nandi-enforce +36 -0
  5. data/lib/generators/nandi/check_constraint/USAGE +19 -0
  6. data/lib/generators/nandi/check_constraint/check_constraint_generator.rb +52 -0
  7. data/lib/generators/nandi/check_constraint/templates/add_check_constraint.rb +15 -0
  8. data/lib/generators/nandi/check_constraint/templates/validate_check_constraint.rb +9 -0
  9. data/lib/generators/nandi/compile/USAGE +19 -0
  10. data/lib/generators/nandi/compile/compile_generator.rb +62 -0
  11. data/lib/generators/nandi/foreign_key/USAGE +47 -0
  12. data/lib/generators/nandi/foreign_key/foreign_key_generator.rb +91 -0
  13. data/lib/generators/nandi/foreign_key/templates/add_foreign_key.rb +13 -0
  14. data/lib/generators/nandi/foreign_key/templates/add_reference.rb +11 -0
  15. data/lib/generators/nandi/foreign_key/templates/validate_foreign_key.rb +9 -0
  16. data/lib/generators/nandi/migration/USAGE +9 -0
  17. data/lib/generators/nandi/migration/migration_generator.rb +24 -0
  18. data/lib/generators/nandi/migration/templates/migration.rb +13 -0
  19. data/lib/generators/nandi/not_null_check/USAGE +19 -0
  20. data/lib/generators/nandi/not_null_check/not_null_check_generator.rb +56 -0
  21. data/lib/generators/nandi/not_null_check/templates/add_not_null_check.rb +11 -0
  22. data/lib/generators/nandi/not_null_check/templates/validate_not_null_check.rb +9 -0
  23. data/lib/nandi.rb +35 -0
  24. data/lib/nandi/compiled_migration.rb +86 -0
  25. data/lib/nandi/config.rb +126 -0
  26. data/lib/nandi/file_diff.rb +32 -0
  27. data/lib/nandi/file_matcher.rb +72 -0
  28. data/lib/nandi/formatting.rb +79 -0
  29. data/lib/nandi/instructions.rb +19 -0
  30. data/lib/nandi/instructions/add_check_constraint.rb +23 -0
  31. data/lib/nandi/instructions/add_column.rb +24 -0
  32. data/lib/nandi/instructions/add_foreign_key.rb +40 -0
  33. data/lib/nandi/instructions/add_index.rb +50 -0
  34. data/lib/nandi/instructions/change_column_default.rb +23 -0
  35. data/lib/nandi/instructions/create_table.rb +83 -0
  36. data/lib/nandi/instructions/drop_constraint.rb +22 -0
  37. data/lib/nandi/instructions/drop_table.rb +21 -0
  38. data/lib/nandi/instructions/irreversible_migration.rb +15 -0
  39. data/lib/nandi/instructions/remove_column.rb +23 -0
  40. data/lib/nandi/instructions/remove_index.rb +41 -0
  41. data/lib/nandi/instructions/remove_not_null_constraint.rb +22 -0
  42. data/lib/nandi/instructions/validate_constraint.rb +22 -0
  43. data/lib/nandi/lockfile.rb +58 -0
  44. data/lib/nandi/migration.rb +363 -0
  45. data/lib/nandi/renderers.rb +7 -0
  46. data/lib/nandi/renderers/active_record.rb +13 -0
  47. data/lib/nandi/renderers/active_record/generate.rb +59 -0
  48. data/lib/nandi/renderers/active_record/instructions.rb +134 -0
  49. data/lib/nandi/safe_migration_enforcer.rb +143 -0
  50. data/lib/nandi/timeout_policies.rb +38 -0
  51. data/lib/nandi/timeout_policies/access_exclusive.rb +54 -0
  52. data/lib/nandi/timeout_policies/concurrent.rb +64 -0
  53. data/lib/nandi/validation.rb +10 -0
  54. data/lib/nandi/validation/add_column_validator.rb +43 -0
  55. data/lib/nandi/validation/each_validator.rb +32 -0
  56. data/lib/nandi/validation/failure_helpers.rb +35 -0
  57. data/lib/nandi/validation/remove_index_validator.rb +30 -0
  58. data/lib/nandi/validation/result.rb +30 -0
  59. data/lib/nandi/validation/timeout_validator.rb +37 -0
  60. data/lib/nandi/validator.rb +102 -0
  61. data/lib/nandi/version.rb +5 -0
  62. data/lib/templates/nandi/renderers/active_record/generate/show.rb.erb +27 -0
  63. data/lib/templates/nandi/renderers/active_record/instructions/add_check_constraint/show.rb.erb +7 -0
  64. data/lib/templates/nandi/renderers/active_record/instructions/add_column/show.rb.erb +6 -0
  65. data/lib/templates/nandi/renderers/active_record/instructions/add_foreign_key/show.rb.erb +5 -0
  66. data/lib/templates/nandi/renderers/active_record/instructions/add_index/show.rb.erb +5 -0
  67. data/lib/templates/nandi/renderers/active_record/instructions/change_column_default/show.rb.erb +1 -0
  68. data/lib/templates/nandi/renderers/active_record/instructions/create_table/show.rb.erb +8 -0
  69. data/lib/templates/nandi/renderers/active_record/instructions/drop_constraint/show.rb.erb +4 -0
  70. data/lib/templates/nandi/renderers/active_record/instructions/drop_table/show.rb.erb +1 -0
  71. data/lib/templates/nandi/renderers/active_record/instructions/irreversible_migration/show.rb.erb +1 -0
  72. data/lib/templates/nandi/renderers/active_record/instructions/remove_column/show.rb.erb +5 -0
  73. data/lib/templates/nandi/renderers/active_record/instructions/remove_index/show.rb.erb +4 -0
  74. data/lib/templates/nandi/renderers/active_record/instructions/remove_not_null_constraint/show.rb.erb +1 -0
  75. data/lib/templates/nandi/renderers/active_record/instructions/validate_constraint/show.rb.erb +3 -0
  76. metadata +320 -0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= validate_check_constraint_name.camelize %> < Nandi::Migration
4
+ def up
5
+ validate_constraint <%= format_value(table) %>, <%= format_value(name) %>
6
+ end
7
+
8
+ def down; end
9
+ 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= validate_foreign_key_name.camelize %> < Nandi::Migration
4
+ def up
5
+ validate_constraint <%= format_value(@table) %>, <%= format_value(name) %>
6
+ end
7
+
8
+ def down; end
9
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Generates a new database migration which uses the
3
+ nandi DSL in db/nandi
4
+
5
+ Example:
6
+ rails generate nandi:migration add_stuff_to_things
7
+
8
+ This will create:
9
+ db/safe_migrations/20190424123727_add_stuff_to_things.rb
@@ -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