online_migrations 0.1.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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +112 -0
  3. data/.gitignore +10 -0
  4. data/.rubocop.yml +113 -0
  5. data/.yardopts +1 -0
  6. data/BACKGROUND_MIGRATIONS.md +288 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +27 -0
  9. data/Gemfile.lock +108 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +1067 -0
  12. data/Rakefile +23 -0
  13. data/gemfiles/activerecord_42.gemfile +6 -0
  14. data/gemfiles/activerecord_50.gemfile +5 -0
  15. data/gemfiles/activerecord_51.gemfile +5 -0
  16. data/gemfiles/activerecord_52.gemfile +5 -0
  17. data/gemfiles/activerecord_60.gemfile +5 -0
  18. data/gemfiles/activerecord_61.gemfile +5 -0
  19. data/gemfiles/activerecord_70.gemfile +5 -0
  20. data/gemfiles/activerecord_head.gemfile +5 -0
  21. data/lib/generators/online_migrations/background_migration_generator.rb +29 -0
  22. data/lib/generators/online_migrations/install_generator.rb +34 -0
  23. data/lib/generators/online_migrations/templates/background_migration.rb.tt +22 -0
  24. data/lib/generators/online_migrations/templates/initializer.rb.tt +94 -0
  25. data/lib/generators/online_migrations/templates/migration.rb.tt +46 -0
  26. data/lib/online_migrations/background_migration.rb +64 -0
  27. data/lib/online_migrations/background_migrations/advisory_lock.rb +62 -0
  28. data/lib/online_migrations/background_migrations/backfill_column.rb +52 -0
  29. data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +36 -0
  30. data/lib/online_migrations/background_migrations/config.rb +98 -0
  31. data/lib/online_migrations/background_migrations/copy_column.rb +90 -0
  32. data/lib/online_migrations/background_migrations/migration.rb +210 -0
  33. data/lib/online_migrations/background_migrations/migration_helpers.rb +238 -0
  34. data/lib/online_migrations/background_migrations/migration_job.rb +92 -0
  35. data/lib/online_migrations/background_migrations/migration_job_runner.rb +63 -0
  36. data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +27 -0
  37. data/lib/online_migrations/background_migrations/migration_runner.rb +97 -0
  38. data/lib/online_migrations/background_migrations/migration_status_validator.rb +45 -0
  39. data/lib/online_migrations/background_migrations/scheduler.rb +49 -0
  40. data/lib/online_migrations/batch_iterator.rb +87 -0
  41. data/lib/online_migrations/change_column_type_helpers.rb +587 -0
  42. data/lib/online_migrations/command_checker.rb +590 -0
  43. data/lib/online_migrations/command_recorder.rb +137 -0
  44. data/lib/online_migrations/config.rb +198 -0
  45. data/lib/online_migrations/copy_trigger.rb +91 -0
  46. data/lib/online_migrations/database_tasks.rb +19 -0
  47. data/lib/online_migrations/error_messages.rb +388 -0
  48. data/lib/online_migrations/foreign_key_definition.rb +17 -0
  49. data/lib/online_migrations/foreign_keys_collector.rb +33 -0
  50. data/lib/online_migrations/indexes_collector.rb +48 -0
  51. data/lib/online_migrations/lock_retrier.rb +250 -0
  52. data/lib/online_migrations/migration.rb +63 -0
  53. data/lib/online_migrations/migrator.rb +23 -0
  54. data/lib/online_migrations/schema_cache.rb +96 -0
  55. data/lib/online_migrations/schema_statements.rb +1042 -0
  56. data/lib/online_migrations/utils.rb +140 -0
  57. data/lib/online_migrations/version.rb +5 -0
  58. data/lib/online_migrations.rb +74 -0
  59. data/online_migrations.gemspec +28 -0
  60. metadata +119 -0
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnlineMigrations
4
+ # @private
5
+ module Utils
6
+ class << self
7
+ def ar_version
8
+ ActiveRecord.version.to_s.to_f
9
+ end
10
+
11
+ def developer_env?
12
+ defined?(Rails) && (Rails.env.development? || Rails.env.test?)
13
+ end
14
+
15
+ def say(message)
16
+ message = "[online_migrations] #{message}"
17
+ if (migration = OnlineMigrations.current_migration)
18
+ migration.say(message)
19
+ elsif (logger = ActiveRecord::Base.logger)
20
+ logger.info(message)
21
+ end
22
+ end
23
+
24
+ def warn(message)
25
+ Kernel.warn("[online_migrations] #{message}")
26
+ end
27
+
28
+ def migration_parent
29
+ if ar_version <= 4.2
30
+ ActiveRecord::Migration
31
+ else
32
+ ActiveRecord::Migration[ar_version]
33
+ end
34
+ end
35
+
36
+ def migration_parent_string
37
+ if ar_version <= 4.2
38
+ "ActiveRecord::Migration"
39
+ else
40
+ "ActiveRecord::Migration[#{ar_version}]"
41
+ end
42
+ end
43
+
44
+ def model_parent_string
45
+ if ar_version >= 5.0
46
+ "ApplicationRecord"
47
+ else
48
+ "ActiveRecord::Base"
49
+ end
50
+ end
51
+
52
+ def define_model(connection, table_name)
53
+ Class.new(ActiveRecord::Base) do
54
+ self.table_name = table_name
55
+ self.inheritance_column = :_type_disabled
56
+
57
+ @online_migrations_connection = connection
58
+
59
+ def self.connection
60
+ @online_migrations_connection
61
+ end
62
+ end
63
+ end
64
+
65
+ def to_bool(value)
66
+ value.to_s.match?(/^(true|t|yes|y|1|on)$/i)
67
+ end
68
+
69
+ def foreign_table_name(ref_name, options)
70
+ options.fetch(:to_table) do
71
+ ActiveRecord::Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
72
+ end
73
+ end
74
+
75
+ def ar_partial_writes?
76
+ ActiveRecord::Base.public_send(ar_partial_writes_setting)
77
+ end
78
+
79
+ def ar_partial_writes_setting
80
+ if Utils.ar_version >= 7.0
81
+ "partial_inserts"
82
+ else
83
+ "partial_writes"
84
+ end
85
+ end
86
+
87
+ # Returns estimated rows count for a table.
88
+ # https://www.citusdata.com/blog/2016/10/12/count-performance/
89
+ def estimated_count(connection, table_name)
90
+ quoted_table = connection.quote(table_name)
91
+
92
+ count = connection.select_value(<<~SQL)
93
+ SELECT
94
+ (reltuples / COALESCE(NULLIF(relpages, 0), 1)) *
95
+ (pg_relation_size(#{quoted_table}) / (current_setting('block_size')::integer))
96
+ FROM pg_catalog.pg_class
97
+ WHERE relname = #{quoted_table}
98
+ AND relnamespace = current_schema()::regnamespace
99
+ SQL
100
+ count.to_i if count
101
+ end
102
+
103
+ def ar_where_not_multiple_conditions(relation, conditions)
104
+ if Utils.ar_version >= 6.1
105
+ relation.where.not(conditions)
106
+ else
107
+ # In Active Record < 6.1, NOT with multiple conditions behaves as NOR,
108
+ # which should really behave as NAND.
109
+ # https://www.bigbinary.com/blog/rails-6-deprecates-where-not-working-as-nor-and-will-change-to-nand-in-rails-6-1
110
+ arel_table = relation.arel_table
111
+ conditions = conditions.map { |column, value| arel_table[column].not_eq(value) }
112
+ conditions = conditions.inject(:or)
113
+ relation.where(conditions)
114
+ end
115
+ end
116
+
117
+ FUNCTION_CALL_RE = /(\w+)\s*\(/
118
+ private_constant :FUNCTION_CALL_RE
119
+
120
+ def volatile_default?(connection, type, value)
121
+ return false unless value.is_a?(Proc) || (type.to_s == "uuid" && value.is_a?(String))
122
+
123
+ value = value.call if value.is_a?(Proc)
124
+ return false if !value.is_a?(String)
125
+
126
+ value.scan(FUNCTION_CALL_RE).any? { |(function_name)| volatile_function?(connection, function_name.downcase) }
127
+ end
128
+
129
+ def volatile_function?(connection, function_name)
130
+ query = <<~SQL
131
+ SELECT provolatile
132
+ FROM pg_catalog.pg_proc
133
+ WHERE proname = #{connection.quote(function_name)}
134
+ SQL
135
+
136
+ connection.select_value(query) == "v"
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnlineMigrations
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+
5
+ require "online_migrations/utils"
6
+ require "online_migrations/error_messages"
7
+ require "online_migrations/config"
8
+ require "online_migrations/batch_iterator"
9
+ require "online_migrations/migration"
10
+ require "online_migrations/migrator"
11
+ require "online_migrations/database_tasks"
12
+ require "online_migrations/foreign_key_definition"
13
+ require "online_migrations/foreign_keys_collector"
14
+ require "online_migrations/indexes_collector"
15
+ require "online_migrations/command_checker"
16
+ require "online_migrations/schema_cache"
17
+ require "online_migrations/background_migration"
18
+ require "online_migrations/background_migrations/config"
19
+ require "online_migrations/background_migrations/migration_status_validator"
20
+ require "online_migrations/background_migrations/migration_job_status_validator"
21
+ require "online_migrations/background_migrations/background_migration_class_validator"
22
+ require "online_migrations/background_migrations/backfill_column"
23
+ require "online_migrations/background_migrations/copy_column"
24
+ require "online_migrations/background_migrations/migration_job"
25
+ require "online_migrations/background_migrations/migration"
26
+ require "online_migrations/background_migrations/migration_job_runner"
27
+ require "online_migrations/background_migrations/migration_runner"
28
+ require "online_migrations/background_migrations/migration_helpers"
29
+ require "online_migrations/background_migrations/advisory_lock"
30
+ require "online_migrations/background_migrations/scheduler"
31
+ require "online_migrations/lock_retrier"
32
+ require "online_migrations/command_recorder"
33
+ require "online_migrations/copy_trigger"
34
+ require "online_migrations/change_column_type_helpers"
35
+ require "online_migrations/schema_statements"
36
+ require "online_migrations/version"
37
+
38
+ module OnlineMigrations
39
+ class Error < StandardError; end
40
+ class UnsafeMigration < Error; end
41
+
42
+ class << self
43
+ # @private
44
+ attr_accessor :current_migration
45
+
46
+ def configure
47
+ yield config
48
+ end
49
+
50
+ def config
51
+ @config ||= Config.new
52
+ end
53
+
54
+ def load
55
+ require "active_record/connection_adapters/postgresql_adapter"
56
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(OnlineMigrations::SchemaStatements)
57
+
58
+ ActiveRecord::Migration.prepend(OnlineMigrations::Migration)
59
+ ActiveRecord::Migrator.prepend(OnlineMigrations::Migrator)
60
+
61
+ ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(OnlineMigrations::DatabaseTasks)
62
+ ActiveRecord::ConnectionAdapters::SchemaCache.prepend(OnlineMigrations::SchemaCache)
63
+ ActiveRecord::Migration::CommandRecorder.include(OnlineMigrations::CommandRecorder)
64
+
65
+ if OnlineMigrations::Utils.ar_version <= 5.1
66
+ ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.prepend(OnlineMigrations::ForeignKeyDefinition)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ ActiveSupport.on_load(:active_record) do
73
+ OnlineMigrations.load
74
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/online_migrations/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "online_migrations"
7
+ spec.version = OnlineMigrations::VERSION
8
+ spec.authors = ["fatkodima"]
9
+ spec.email = ["fatkodima123@gmail.com"]
10
+
11
+ spec.summary = "Catch unsafe PostgreSQL migrations in development and run them easier in production"
12
+ spec.homepage = "https://github.com/fatkodima/online_migrations"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.1.0")
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_dependency "activerecord", ">= 4.2"
28
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: online_migrations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - fatkodima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ description:
28
+ email:
29
+ - fatkodima123@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".github/workflows/test.yml"
35
+ - ".gitignore"
36
+ - ".rubocop.yml"
37
+ - ".yardopts"
38
+ - BACKGROUND_MIGRATIONS.md
39
+ - CHANGELOG.md
40
+ - Gemfile
41
+ - Gemfile.lock
42
+ - LICENSE.txt
43
+ - README.md
44
+ - Rakefile
45
+ - gemfiles/activerecord_42.gemfile
46
+ - gemfiles/activerecord_50.gemfile
47
+ - gemfiles/activerecord_51.gemfile
48
+ - gemfiles/activerecord_52.gemfile
49
+ - gemfiles/activerecord_60.gemfile
50
+ - gemfiles/activerecord_61.gemfile
51
+ - gemfiles/activerecord_70.gemfile
52
+ - gemfiles/activerecord_head.gemfile
53
+ - lib/generators/online_migrations/background_migration_generator.rb
54
+ - lib/generators/online_migrations/install_generator.rb
55
+ - lib/generators/online_migrations/templates/background_migration.rb.tt
56
+ - lib/generators/online_migrations/templates/initializer.rb.tt
57
+ - lib/generators/online_migrations/templates/migration.rb.tt
58
+ - lib/online_migrations.rb
59
+ - lib/online_migrations/background_migration.rb
60
+ - lib/online_migrations/background_migrations/advisory_lock.rb
61
+ - lib/online_migrations/background_migrations/backfill_column.rb
62
+ - lib/online_migrations/background_migrations/background_migration_class_validator.rb
63
+ - lib/online_migrations/background_migrations/config.rb
64
+ - lib/online_migrations/background_migrations/copy_column.rb
65
+ - lib/online_migrations/background_migrations/migration.rb
66
+ - lib/online_migrations/background_migrations/migration_helpers.rb
67
+ - lib/online_migrations/background_migrations/migration_job.rb
68
+ - lib/online_migrations/background_migrations/migration_job_runner.rb
69
+ - lib/online_migrations/background_migrations/migration_job_status_validator.rb
70
+ - lib/online_migrations/background_migrations/migration_runner.rb
71
+ - lib/online_migrations/background_migrations/migration_status_validator.rb
72
+ - lib/online_migrations/background_migrations/scheduler.rb
73
+ - lib/online_migrations/batch_iterator.rb
74
+ - lib/online_migrations/change_column_type_helpers.rb
75
+ - lib/online_migrations/command_checker.rb
76
+ - lib/online_migrations/command_recorder.rb
77
+ - lib/online_migrations/config.rb
78
+ - lib/online_migrations/copy_trigger.rb
79
+ - lib/online_migrations/database_tasks.rb
80
+ - lib/online_migrations/error_messages.rb
81
+ - lib/online_migrations/foreign_key_definition.rb
82
+ - lib/online_migrations/foreign_keys_collector.rb
83
+ - lib/online_migrations/indexes_collector.rb
84
+ - lib/online_migrations/lock_retrier.rb
85
+ - lib/online_migrations/migration.rb
86
+ - lib/online_migrations/migrator.rb
87
+ - lib/online_migrations/schema_cache.rb
88
+ - lib/online_migrations/schema_statements.rb
89
+ - lib/online_migrations/utils.rb
90
+ - lib/online_migrations/version.rb
91
+ - online_migrations.gemspec
92
+ homepage: https://github.com/fatkodima/online_migrations
93
+ licenses:
94
+ - MIT
95
+ metadata:
96
+ homepage_uri: https://github.com/fatkodima/online_migrations
97
+ source_code_uri: https://github.com/fatkodima/online_migrations
98
+ changelog_uri: https://github.com/fatkodima/online_migrations/blob/master/CHANGELOG.md
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.1.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.1.6
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Catch unsafe PostgreSQL migrations in development and run them easier in
118
+ production
119
+ test_files: []