alterity 0.0.0 → 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 +4 -4
- data/lib/alterity.rb +77 -1
- data/lib/alterity/configuration.rb +63 -0
- data/lib/alterity/mysql_client_additions.rb +11 -0
- data/lib/alterity/railtie.rb +30 -0
- data/lib/alterity/version.rb +2 -2
- data/spec/alterity_spec.rb +35 -1
- data/spec/bin/rails_app_migration_test.sh +7 -0
- data/spec/spec_helper.rb +4 -0
- metadata +13 -20
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 61d1573038462b1d92925a95f8b67d8c0d53ffc250c429a742bb9a3e19fe715a
         | 
| 4 | 
            +
              data.tar.gz: e6cf943aae6b2ead8e6c04d335a63c1c1438c296381e066c513d0aefb99e6d13
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ea2e19e99394e1cacd831855e509363c4d164dabfe564ef7ac47945c61c9fc94071d965a3bc16efdd80530108fecd50ee582900d962f3fd63571ae1e6d71203f
         | 
| 7 | 
            +
              data.tar.gz: bd8f34566ced8252a587804d966c02e850d41a4d0faaf88bd0279886ea58286c4d70a7ca15b7ceafc17934bdce34f69b459b7546a1603acaee4b91ab26f89933
         | 
    
        data/lib/alterity.rb
    CHANGED
    
    | @@ -1,4 +1,80 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            require "rails"
         | 
| 4 | 
            +
            require "alterity/configuration"
         | 
| 5 | 
            +
            require "alterity/mysql_client_additions"
         | 
| 6 | 
            +
            require "alterity/railtie"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            class Alterity
         | 
| 9 | 
            +
              class << self
         | 
| 10 | 
            +
                def process_sql_query(sql, &block)
         | 
| 11 | 
            +
                  case sql.strip
         | 
| 12 | 
            +
                  when /^alter table (?<table>.+?) (?<updates>.+)/i
         | 
| 13 | 
            +
                    execute_alter($~[:table], $~[:updates])
         | 
| 14 | 
            +
                  when /^create index (?<index>.+?) on (?<table>.+?) (?<updates>.+)/i
         | 
| 15 | 
            +
                    execute_alter($~[:table], "ADD INDEX #{$~[:index]} #{$~[:updates]}")
         | 
| 16 | 
            +
                  when /^create unique index (?<index>.+?) on (?<table>.+?) (?<updates>.+)/i
         | 
| 17 | 
            +
                    execute_alter($~[:table], "ADD UNIQUE INDEX #{$~[:index]} #{$~[:updates]}")
         | 
| 18 | 
            +
                  when /^drop index (?<index>.+?) on (?<table>.+)/i
         | 
| 19 | 
            +
                    execute_alter($~[:table], "DROP INDEX #{$~[:index]}")
         | 
| 20 | 
            +
                  else
         | 
| 21 | 
            +
                    block.call
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                # hooks
         | 
| 26 | 
            +
                def before_running_migrations
         | 
| 27 | 
            +
                  state.migrating = true
         | 
| 28 | 
            +
                  set_database_config
         | 
| 29 | 
            +
                  prepare_replicas_dsns_table
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def after_running_migrations
         | 
| 33 | 
            +
                  state.migrating = false
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                private
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def execute_alter(table, updates)
         | 
| 39 | 
            +
                  altered_table = table.delete("`")
         | 
| 40 | 
            +
                  alter_argument = %("#{updates.gsub('"', '\\"').gsub('`', '\\\`')}")
         | 
| 41 | 
            +
                  prepared_command = config.command.call(config, altered_table, alter_argument).gsub(/\n/, "\\\n")
         | 
| 42 | 
            +
                  puts "[Alterity] Will execute: #{prepared_command}"
         | 
| 43 | 
            +
                  system(prepared_command)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def set_database_config
         | 
| 47 | 
            +
                  db_config_hash = ActiveRecord::Base.connection_db_config.configuration_hash
         | 
| 48 | 
            +
                  %i[host port database username password].each do |key|
         | 
| 49 | 
            +
                    config[key] = db_config_hash[key]
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # Optional: Automatically set up table PT-OSC will monitor for replica lag.
         | 
| 54 | 
            +
                def prepare_replicas_dsns_table
         | 
| 55 | 
            +
                  return if config.replicas_dsns_table.blank?
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  database = config.replicas_dsns_database
         | 
| 58 | 
            +
                  table = "#{database}.#{config.replicas_dsns_table}"
         | 
| 59 | 
            +
                  connection = ActiveRecord::Base.connection
         | 
| 60 | 
            +
                  connection.execute "CREATE DATABASE IF NOT EXISTS #{database}"
         | 
| 61 | 
            +
                  connection.execute <<~SQL
         | 
| 62 | 
            +
                    CREATE TABLE IF NOT EXISTS #{table} (
         | 
| 63 | 
            +
                      id INT(11) NOT NULL AUTO_INCREMENT,
         | 
| 64 | 
            +
                      parent_id INT(11) DEFAULT NULL,
         | 
| 65 | 
            +
                      dsn VARCHAR(255) NOT NULL,
         | 
| 66 | 
            +
                      PRIMARY KEY (id)
         | 
| 67 | 
            +
                    ) ENGINE=InnoDB
         | 
| 68 | 
            +
                  SQL
         | 
| 69 | 
            +
                  connection.execute "TRUNCATE #{table}"
         | 
| 70 | 
            +
                  return if config.replicas_dsns.empty?
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  connection.execute <<~SQL
         | 
| 73 | 
            +
                    INSERT INTO #{table} (dsn)
         | 
| 74 | 
            +
                     #{config.replicas_dsns.map { |dsn| "('#{dsn}')" }.join(',')}
         | 
| 75 | 
            +
                  SQL
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              reset_state_and_configuration
         | 
| 4 80 | 
             
            end
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Alterity
         | 
| 4 | 
            +
              Configuration = Struct.new(
         | 
| 5 | 
            +
                :command,
         | 
| 6 | 
            +
                :host, :port, :database, :username, :password,
         | 
| 7 | 
            +
                :replicas_dsns_database, :replicas_dsns_table, :replicas_dsns
         | 
| 8 | 
            +
              )
         | 
| 9 | 
            +
              CurrentState = Struct.new(:migrating, :disabled)
         | 
| 10 | 
            +
              cattr_accessor :state
         | 
| 11 | 
            +
              cattr_accessor :config
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              class << self
         | 
| 14 | 
            +
                def reset_state_and_configuration
         | 
| 15 | 
            +
                  self.config = Configuration.new
         | 
| 16 | 
            +
                  self.state = CurrentState.new
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  config.command = lambda { |config, altered_table, alter_argument|
         | 
| 19 | 
            +
                    <<~SHELL.squish
         | 
| 20 | 
            +
                      pt-online-schema-change
         | 
| 21 | 
            +
                        -h #{config.host}
         | 
| 22 | 
            +
                        -P #{config.port}
         | 
| 23 | 
            +
                        -u #{config.username}
         | 
| 24 | 
            +
                        --password=#{config.password}
         | 
| 25 | 
            +
                        --execute
         | 
| 26 | 
            +
                        D=#{config.database},t=#{altered_table}
         | 
| 27 | 
            +
                        --alter #{alter_argument}
         | 
| 28 | 
            +
                    SHELL
         | 
| 29 | 
            +
                  }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def configure
         | 
| 33 | 
            +
                  yield self
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def command=(new_command)
         | 
| 37 | 
            +
                  config.command = new_command
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def replicas_dsns_table(database:, table:, dsns:)
         | 
| 41 | 
            +
                  return ArgumentError.new("database & table must be present") if database.blank? || table.blank?
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  config.replicas_dsns_database = database
         | 
| 44 | 
            +
                  config.replicas_dsns_table = table
         | 
| 45 | 
            +
                  config.replicas_dsns = dsns.uniq.map do |dsn|
         | 
| 46 | 
            +
                    parts = dsn.split(",")
         | 
| 47 | 
            +
                    # automatically add default port
         | 
| 48 | 
            +
                    parts << "P=3306" unless parts.any? { |part| part.start_with?("P=") }
         | 
| 49 | 
            +
                    # automatically remove master
         | 
| 50 | 
            +
                    next if parts.include?("h=#{config.host}") && parts.include?("P=#{config.port}")
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    parts.join(",")
         | 
| 53 | 
            +
                  end.compact
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def disable
         | 
| 57 | 
            +
                  state.disabled = true
         | 
| 58 | 
            +
                  yield
         | 
| 59 | 
            +
                ensure
         | 
| 60 | 
            +
                  state.disabled = false
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Alterity
         | 
| 4 | 
            +
              class Railtie < Rails::Railtie
         | 
| 5 | 
            +
                railtie_name :alterity
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                rake_tasks do
         | 
| 8 | 
            +
                  namespace :alterity do
         | 
| 9 | 
            +
                    task :intercept_table_alterations do
         | 
| 10 | 
            +
                      Alterity.before_running_migrations
         | 
| 11 | 
            +
                      Rake::Task["alterity:stop_intercepting_table_alterations"].reenable
         | 
| 12 | 
            +
                      ::Mysql2::Client.prepend(Alterity::MysqlClientAdditions)
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    task :stop_intercepting_table_alterations do
         | 
| 16 | 
            +
                      Rake::Task["alterity:intercept_table_alterations"].reenable
         | 
| 17 | 
            +
                      Alterity.after_running_migrations
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  unless %w[1 true].include?(ENV["DISABLE_ALTERITY"])
         | 
| 22 | 
            +
                    ["migrate", "migrate:up", "migrate:down", "migrate:redo", "rollback"].each do |task|
         | 
| 23 | 
            +
                      Rake::Task["db:#{task}"].enhance(["alterity:intercept_table_alterations"]) do
         | 
| 24 | 
            +
                        Rake::Task["alterity:stop_intercepting_table_alterations"].invoke
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
    
        data/lib/alterity/version.rb
    CHANGED
    
    
    
        data/spec/alterity_spec.rb
    CHANGED
    
    | @@ -1,5 +1,39 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            RSpec.describe Alterity do
         | 
| 4 | 
            -
               | 
| 4 | 
            +
              describe ".process_sql_query" do
         | 
| 5 | 
            +
                it "executes command on table altering queries" do
         | 
| 6 | 
            +
                  [
         | 
| 7 | 
            +
                    ["ALTER TABLE `users` ADD `col` VARCHAR(255)", "`users`", "ADD `col` VARCHAR(255)"],
         | 
| 8 | 
            +
                    ["ALTER TABLE `users` ADD `col0` INT(11), DROP `col1`", "`users`", "ADD `col0` INT(11), DROP `col1`"],
         | 
| 9 | 
            +
                    ["CREATE INDEX `idx_users_on_col` ON `users` (col)", "`users`", "ADD INDEX `idx_users_on_col` (col)"],
         | 
| 10 | 
            +
                    ["CREATE UNIQUE INDEX `idx_users_on_col` ON `users` (col)", "`users`", "ADD UNIQUE INDEX `idx_users_on_col` (col)"],
         | 
| 11 | 
            +
                    ["DROP INDEX `idx_users_on_col` ON `users`", "`users`", "DROP INDEX `idx_users_on_col`"],
         | 
| 12 | 
            +
                    ["alter table users drop col", "users", "drop col"]
         | 
| 13 | 
            +
                  ].each do |(query, expected_table, expected_updates)|
         | 
| 14 | 
            +
                    expected_block = proc {}
         | 
| 15 | 
            +
                    expect(expected_block).not_to receive(:call)
         | 
| 16 | 
            +
                    expect(Alterity).to receive(:execute_alter).with(expected_table, expected_updates)
         | 
| 17 | 
            +
                    Alterity.process_sql_query(query, &expected_block)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                it "ignores non-altering queries" do
         | 
| 22 | 
            +
                  [
         | 
| 23 | 
            +
                    "select * from users",
         | 
| 24 | 
            +
                    "insert into users values (1)",
         | 
| 25 | 
            +
                    "delete from users",
         | 
| 26 | 
            +
                    "begin",
         | 
| 27 | 
            +
                    "SHOW CREATE TABLE `users`",
         | 
| 28 | 
            +
                    "SHOW TABLE STATUS LIKE `users`",
         | 
| 29 | 
            +
                    "SHOW KEYS FROM `users`",
         | 
| 30 | 
            +
                    "SHOW FULL FIELDS FROM `users`"
         | 
| 31 | 
            +
                  ].each do |query|
         | 
| 32 | 
            +
                    expected_block = proc {}
         | 
| 33 | 
            +
                    expect(expected_block).to receive(:call)
         | 
| 34 | 
            +
                    expect(Alterity).not_to receive(:execute_alter)
         | 
| 35 | 
            +
                    Alterity.process_sql_query(query, &expected_block)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 5 39 | 
             
            end
         | 
    
        data/spec/spec_helper.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,55 +1,43 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: alterity
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.9.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Chris Maximin
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2021- | 
| 11 | 
            +
            date: 2021-04-05 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            -
              name:  | 
| 14 | 
            +
              name: mysql2
         | 
| 15 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - ">="
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: ' | 
| 20 | 
            -
                - - "<"
         | 
| 21 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 22 | 
            -
                    version: '8'
         | 
| 19 | 
            +
                    version: '0.3'
         | 
| 23 20 | 
             
              type: :runtime
         | 
| 24 21 | 
             
              prerelease: false
         | 
| 25 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 26 23 | 
             
                requirements:
         | 
| 27 24 | 
             
                - - ">="
         | 
| 28 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 29 | 
            -
                    version: ' | 
| 30 | 
            -
                - - "<"
         | 
| 31 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 32 | 
            -
                    version: '8'
         | 
| 26 | 
            +
                    version: '0.3'
         | 
| 33 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 34 | 
            -
              name:  | 
| 28 | 
            +
              name: rails
         | 
| 35 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 36 30 | 
             
                requirements:
         | 
| 37 | 
            -
                - - "~>"
         | 
| 38 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 39 | 
            -
                    version: '0.5'
         | 
| 40 31 | 
             
                - - ">="
         | 
| 41 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 42 | 
            -
                    version:  | 
| 33 | 
            +
                    version: '5'
         | 
| 43 34 | 
             
              type: :runtime
         | 
| 44 35 | 
             
              prerelease: false
         | 
| 45 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 46 37 | 
             
                requirements:
         | 
| 47 | 
            -
                - - "~>"
         | 
| 48 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 49 | 
            -
                    version: '0.5'
         | 
| 50 38 | 
             
                - - ">="
         | 
| 51 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 52 | 
            -
                    version:  | 
| 40 | 
            +
                    version: '5'
         | 
| 53 41 | 
             
            description: Execute your ActiveRecord migrations with Percona's pt-online-schema-change.
         | 
| 54 42 | 
             
            email:
         | 
| 55 43 | 
             
            - gems@chrismaximin.com
         | 
| @@ -58,8 +46,12 @@ extensions: [] | |
| 58 46 | 
             
            extra_rdoc_files: []
         | 
| 59 47 | 
             
            files:
         | 
| 60 48 | 
             
            - lib/alterity.rb
         | 
| 49 | 
            +
            - lib/alterity/configuration.rb
         | 
| 50 | 
            +
            - lib/alterity/mysql_client_additions.rb
         | 
| 51 | 
            +
            - lib/alterity/railtie.rb
         | 
| 61 52 | 
             
            - lib/alterity/version.rb
         | 
| 62 53 | 
             
            - spec/alterity_spec.rb
         | 
| 54 | 
            +
            - spec/bin/rails_app_migration_test.sh
         | 
| 63 55 | 
             
            - spec/spec_helper.rb
         | 
| 64 56 | 
             
            homepage: https://github.com/gumroad/alterity
         | 
| 65 57 | 
             
            licenses:
         | 
| @@ -89,4 +81,5 @@ specification_version: 4 | |
| 89 81 | 
             
            summary: Execute your ActiveRecord migrations with Percona's pt-online-schema-change.
         | 
| 90 82 | 
             
            test_files:
         | 
| 91 83 | 
             
            - spec/alterity_spec.rb
         | 
| 84 | 
            +
            - spec/bin/rails_app_migration_test.sh
         | 
| 92 85 | 
             
            - spec/spec_helper.rb
         |