alterity 0.9.0 → 1.0.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 +8 -8
- data/lib/alterity/configuration.rb +20 -30
- data/lib/alterity/default_configuration.rb +15 -0
- data/lib/alterity/mysql_client_additions.rb +2 -0
- data/lib/alterity/version.rb +1 -1
- data/spec/alterity_spec.rb +3 -1
- data/spec/bin/custom_config.rb +21 -0
- data/spec/bin/rails_app_migration_test.sh +64 -3
- data/spec/bin/test_custom_config_result.rb +20 -0
- metadata +8 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b468009cfd8631b397ea59fcb1750aada7238d5b8910f6cc492cf4778bdc2fa6
         | 
| 4 | 
            +
              data.tar.gz: 02e17258c42233d8198ad62cf15841d6c3fe78b4b2730f64100e1771693686a1
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 8929653022fbcec5f31c5583c619d252547928319276b020021404ad38cdbf2a2fae1861f9748009fcc97f99c0bc7a07f698397d6c8d20d41bbd96251b7a3693
         | 
| 7 | 
            +
              data.tar.gz: ec349e1dfee9361ef937e55fe8911da437fad1c7dfc74446ff7f7ce2608943e0755f238297e5073f8786b2a6a0d9dc71c6785a1b91f1d7b04efe4b4a548a1b46
         | 
    
        data/lib/alterity.rb
    CHANGED
    
    | @@ -8,14 +8,14 @@ require "alterity/railtie" | |
| 8 8 | 
             
            class Alterity
         | 
| 9 9 | 
             
              class << self
         | 
| 10 10 | 
             
                def process_sql_query(sql, &block)
         | 
| 11 | 
            -
                  case sql.strip
         | 
| 12 | 
            -
                  when /^alter | 
| 11 | 
            +
                  case sql.tr("\n", " ").strip
         | 
| 12 | 
            +
                  when /^alter\s+table\s+(?<table>.+?)\s+(?<updates>.+)/i
         | 
| 13 13 | 
             
                    execute_alter($~[:table], $~[:updates])
         | 
| 14 | 
            -
                  when /^create | 
| 14 | 
            +
                  when /^create\s+index\s+(?<index>.+?)\s+on\s+(?<table>.+?)\s+(?<updates>.+)/i
         | 
| 15 15 | 
             
                    execute_alter($~[:table], "ADD INDEX #{$~[:index]} #{$~[:updates]}")
         | 
| 16 | 
            -
                  when /^create | 
| 16 | 
            +
                  when /^create\s+unique\s+index\s+(?<index>.+?)\s+on\s+(?<table>.+?)\s+(?<updates>.+)/i
         | 
| 17 17 | 
             
                    execute_alter($~[:table], "ADD UNIQUE INDEX #{$~[:index]} #{$~[:updates]}")
         | 
| 18 | 
            -
                  when /^drop | 
| 18 | 
            +
                  when /^drop\s+index\s+(?<index>.+?)\s+on\s+(?<table>.+)/i
         | 
| 19 19 | 
             
                    execute_alter($~[:table], "DROP INDEX #{$~[:index]}")
         | 
| 20 20 | 
             
                  else
         | 
| 21 21 | 
             
                    block.call
         | 
| @@ -38,9 +38,9 @@ class Alterity | |
| 38 38 | 
             
                def execute_alter(table, updates)
         | 
| 39 39 | 
             
                  altered_table = table.delete("`")
         | 
| 40 40 | 
             
                  alter_argument = %("#{updates.gsub('"', '\\"').gsub('`', '\\\`')}")
         | 
| 41 | 
            -
                  prepared_command = config.command.call( | 
| 41 | 
            +
                  prepared_command = config.command.call(altered_table, alter_argument).to_s.gsub(/\n/, "\\\n")
         | 
| 42 42 | 
             
                  puts "[Alterity] Will execute: #{prepared_command}"
         | 
| 43 | 
            -
                  system(prepared_command)
         | 
| 43 | 
            +
                  system(prepared_command) || raise("[Alterity] Command failed")
         | 
| 44 44 | 
             
                end
         | 
| 45 45 |  | 
| 46 46 | 
             
                def set_database_config
         | 
| @@ -70,7 +70,7 @@ class Alterity | |
| 70 70 | 
             
                  return if config.replicas_dsns.empty?
         | 
| 71 71 |  | 
| 72 72 | 
             
                  connection.execute <<~SQL
         | 
| 73 | 
            -
                    INSERT INTO #{table} (dsn)
         | 
| 73 | 
            +
                    INSERT INTO #{table} (dsn) VALUES
         | 
| 74 74 | 
             
                     #{config.replicas_dsns.map { |dsn| "('#{dsn}')" }.join(',')}
         | 
| 75 75 | 
             
                  SQL
         | 
| 76 76 | 
             
                end
         | 
| @@ -13,46 +13,36 @@ class Alterity | |
| 13 13 | 
             
              class << self
         | 
| 14 14 | 
             
                def reset_state_and_configuration
         | 
| 15 15 | 
             
                  self.config = Configuration.new
         | 
| 16 | 
            -
                   | 
| 16 | 
            +
                  class << config
         | 
| 17 | 
            +
                    def replicas(database:, table:, dsns:)
         | 
| 18 | 
            +
                      return ArgumentError.new("database & table must be present") if database.blank? || table.blank?
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      self.replicas_dsns_database = database
         | 
| 21 | 
            +
                      self.replicas_dsns_table = table
         | 
| 22 | 
            +
                      self.replicas_dsns = dsns.uniq.map do |dsn|
         | 
| 23 | 
            +
                        parts = dsn.split(",")
         | 
| 24 | 
            +
                        # automatically add default port
         | 
| 25 | 
            +
                        parts << "P=3306" unless parts.any? { |part| part.start_with?("P=") }
         | 
| 26 | 
            +
                        # automatically remove master
         | 
| 27 | 
            +
                        next if parts.include?("h=#{host}") && parts.include?("P=#{port}")
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                        parts.join(",")
         | 
| 30 | 
            +
                      end.compact
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 17 33 |  | 
| 18 | 
            -
                   | 
| 19 | 
            -
             | 
| 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 | 
            -
                  }
         | 
| 34 | 
            +
                  self.state = CurrentState.new
         | 
| 35 | 
            +
                  load "#{__dir__}/default_configuration.rb"
         | 
| 30 36 | 
             
                end
         | 
| 31 37 |  | 
| 32 38 | 
             
                def configure
         | 
| 33 | 
            -
                  yield  | 
| 39 | 
            +
                  yield config
         | 
| 34 40 | 
             
                end
         | 
| 35 41 |  | 
| 36 42 | 
             
                def command=(new_command)
         | 
| 37 43 | 
             
                  config.command = new_command
         | 
| 38 44 | 
             
                end
         | 
| 39 45 |  | 
| 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 46 | 
             
                def disable
         | 
| 57 47 | 
             
                  state.disabled = true
         | 
| 58 48 | 
             
                  yield
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Alterity.configure do |config|
         | 
| 4 | 
            +
              config.command = lambda { |altered_table, alter_argument|
         | 
| 5 | 
            +
                parts = ["pt-online-schema-change"]
         | 
| 6 | 
            +
                parts << %(-h "#{config.host}") if config.host.present?
         | 
| 7 | 
            +
                parts << %(-P "#{config.port}") if config.port.present?
         | 
| 8 | 
            +
                parts << %(-u "#{config.username}") if config.username.present?
         | 
| 9 | 
            +
                parts << %(--password "#{config.password.gsub('"', '\\"')}") if config.password.present?
         | 
| 10 | 
            +
                parts << "--execute"
         | 
| 11 | 
            +
                parts << "D=#{config.database},t=#{altered_table}"
         | 
| 12 | 
            +
                parts << "--alter #{alter_argument}"
         | 
| 13 | 
            +
                parts.join(" ")
         | 
| 14 | 
            +
              }
         | 
| 15 | 
            +
            end
         | 
    
        data/lib/alterity/version.rb
    CHANGED
    
    
    
        data/spec/alterity_spec.rb
    CHANGED
    
    | @@ -9,8 +9,10 @@ RSpec.describe Alterity do | |
| 9 9 | 
             
                    ["CREATE INDEX `idx_users_on_col` ON `users` (col)", "`users`", "ADD INDEX `idx_users_on_col` (col)"],
         | 
| 10 10 | 
             
                    ["CREATE UNIQUE INDEX `idx_users_on_col` ON `users` (col)", "`users`", "ADD UNIQUE INDEX `idx_users_on_col` (col)"],
         | 
| 11 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"]
         | 
| 12 | 
            +
                    ["alter table users drop col", "users", "drop col"],
         | 
| 13 | 
            +
                    ["  ALTER TABLE\n   users\n   DROP col", "users", "DROP col"]
         | 
| 13 14 | 
             
                  ].each do |(query, expected_table, expected_updates)|
         | 
| 15 | 
            +
                    puts query.inspect
         | 
| 14 16 | 
             
                    expected_block = proc {}
         | 
| 15 17 | 
             
                    expect(expected_block).not_to receive(:call)
         | 
| 16 18 | 
             
                    expect(Alterity).to receive(:execute_alter).with(expected_table, expected_updates)
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Alterity.configure do |config|
         | 
| 4 | 
            +
              config.command = lambda { |altered_table, alter_argument|
         | 
| 5 | 
            +
                string = config.to_h.slice(
         | 
| 6 | 
            +
                  *%i[host port username database replicas_dsns_database replicas_dsns_table replicas_dsns]
         | 
| 7 | 
            +
                ).to_s
         | 
| 8 | 
            +
                system("echo '#{string}' > /tmp/custom_command_result.txt")
         | 
| 9 | 
            +
                system("echo '#{altered_table}' >> /tmp/custom_command_result.txt")
         | 
| 10 | 
            +
                system("echo '#{alter_argument}' >> /tmp/custom_command_result.txt")
         | 
| 11 | 
            +
              }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              config.replicas(
         | 
| 14 | 
            +
                database: "percona",
         | 
| 15 | 
            +
                table: "replicas_dsns",
         | 
| 16 | 
            +
                dsns: [
         | 
| 17 | 
            +
                  "h=host1",
         | 
| 18 | 
            +
                  "h=host2"
         | 
| 19 | 
            +
                ]
         | 
| 20 | 
            +
              )
         | 
| 21 | 
            +
            end
         | 
| @@ -1,7 +1,68 @@ | |
| 1 1 | 
             
            #!/usr/bin/env bash
         | 
| 2 2 |  | 
| 3 | 
            -
            set - | 
| 3 | 
            +
            set -euox pipefail
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            unset BUNDLE_GEMFILE # because we're going to create a new rails app here and use bundler
         | 
| 4 6 |  | 
| 5 | 
            -
            printenv
         | 
| 6 | 
            -
            sudo apt update
         | 
| 7 7 | 
             
            sudo apt install percona-toolkit
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # Fixes: `Cannot connect to MySQL: Cannot get MySQL var character_set_server: DBD::mysql::db selectrow_array failed: Table 'performance_schema.session_variables' doesn't exist [for Statement "SHOW VARIABLES LIKE 'character_set_server'"] at /usr/local/Cellar/percona-toolkit/3.3.0/libexec/bin/pt-online-schema-change line 2415.`
         | 
| 10 | 
            +
            mysql -h $MYSQL_HOST -u $MYSQL_USERNAME -e 'set @@global.show_compatibility_56=ON'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            gem install rails -v $RAILS_VERSION
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            rails new testapp \
         | 
| 15 | 
            +
              --skip-action-mailer \
         | 
| 16 | 
            +
              --skip-action-mailbox \
         | 
| 17 | 
            +
              --skip-action-text \
         | 
| 18 | 
            +
              --skip-active-job \
         | 
| 19 | 
            +
              --skip-active-storage \
         | 
| 20 | 
            +
              --skip-puma \
         | 
| 21 | 
            +
              --skip-action-cable \
         | 
| 22 | 
            +
              --skip-sprockets \
         | 
| 23 | 
            +
              --skip-spring \
         | 
| 24 | 
            +
              --skip-listen--skip-javascript \
         | 
| 25 | 
            +
              --skip-turbolinks \
         | 
| 26 | 
            +
              --skip-jbuilder--skip-test \
         | 
| 27 | 
            +
              --skip-system-test \
         | 
| 28 | 
            +
              --skip-bootsnap \
         | 
| 29 | 
            +
              --skip-javascript \
         | 
| 30 | 
            +
              --skip-webpack-install
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            cd testapp
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            # Sanity check:
         | 
| 35 | 
            +
            # echo 'gem "mysql2"' >> Gemfile
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            echo 'gem "alterity", path: "../"' >> Gemfile
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            bundle
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            # Local machine test
         | 
| 42 | 
            +
            # echo 'development:
         | 
| 43 | 
            +
            #   adapter: mysql2
         | 
| 44 | 
            +
            #   database: alterity_test' > config/database.yml
         | 
| 45 | 
            +
            # bundle e rails db:drop db:create
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            echo 'development:
         | 
| 48 | 
            +
              adapter: mysql2
         | 
| 49 | 
            +
              database: <%= ENV.fetch("MYSQL_DATABASE") %>
         | 
| 50 | 
            +
              host: <%= ENV.fetch("MYSQL_HOST") %>
         | 
| 51 | 
            +
              username: <%= ENV.fetch("MYSQL_USERNAME") %>' > config/database.yml
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            bundle e rails g model shirt
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            bundle e rails g migration add_color_to_shirts color:string
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            # Test default configuration works as expected
         | 
| 58 | 
            +
            bundle e rails db:migrate --trace
         | 
| 59 | 
            +
            rails runner 'Shirt.columns.map(&:name).include?("color") || exit(1)'
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            # Now test custom command and replication setup
         | 
| 62 | 
            +
            cp ../spec/bin/custom_config.rb config/initializers/alterity.rb
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            bundle e rails g migration add_color2_to_shirts color2:string
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            bundle e rails db:migrate --trace
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            ruby ../spec/bin/test_custom_config_result.rb
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            result = File.read("/tmp/custom_command_result.txt").downcase.strip
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            expected_result = %({:host=>"127.0.0.1", :port=>nil, :username=>"root", :database=>"alterity_test", :replicas_dsns_database=>"percona", :replicas_dsns_table=>"replicas_dsns", :replicas_dsns=>["h=host1,P=3306", "h=host2,P=3306"]}
         | 
| 6 | 
            +
            shirts
         | 
| 7 | 
            +
            "ADD \\`color2\\` VARCHAR(255)").downcase.strip
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            puts "Expected custom config result:"
         | 
| 10 | 
            +
            puts expected_result
         | 
| 11 | 
            +
            p expected_result.chars.map(&:hex)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            puts "Custom config result:"
         | 
| 14 | 
            +
            puts result
         | 
| 15 | 
            +
            p result.chars.map(&:hex)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            if result != expected_result
         | 
| 18 | 
            +
              puts "=> mismatch"
         | 
| 19 | 
            +
              exit(1)
         | 
| 20 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: alterity
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 1.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Chris Maximin
         | 
| @@ -30,14 +30,14 @@ dependencies: | |
| 30 30 | 
             
                requirements:
         | 
| 31 31 | 
             
                - - ">="
         | 
| 32 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            -
                    version: ' | 
| 33 | 
            +
                    version: '6.1'
         | 
| 34 34 | 
             
              type: :runtime
         | 
| 35 35 | 
             
              prerelease: false
         | 
| 36 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 37 | 
             
                requirements:
         | 
| 38 38 | 
             
                - - ">="
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            -
                    version: ' | 
| 40 | 
            +
                    version: '6.1'
         | 
| 41 41 | 
             
            description: Execute your ActiveRecord migrations with Percona's pt-online-schema-change.
         | 
| 42 42 | 
             
            email:
         | 
| 43 43 | 
             
            - gems@chrismaximin.com
         | 
| @@ -47,11 +47,14 @@ extra_rdoc_files: [] | |
| 47 47 | 
             
            files:
         | 
| 48 48 | 
             
            - lib/alterity.rb
         | 
| 49 49 | 
             
            - lib/alterity/configuration.rb
         | 
| 50 | 
            +
            - lib/alterity/default_configuration.rb
         | 
| 50 51 | 
             
            - lib/alterity/mysql_client_additions.rb
         | 
| 51 52 | 
             
            - lib/alterity/railtie.rb
         | 
| 52 53 | 
             
            - lib/alterity/version.rb
         | 
| 53 54 | 
             
            - spec/alterity_spec.rb
         | 
| 55 | 
            +
            - spec/bin/custom_config.rb
         | 
| 54 56 | 
             
            - spec/bin/rails_app_migration_test.sh
         | 
| 57 | 
            +
            - spec/bin/test_custom_config_result.rb
         | 
| 55 58 | 
             
            - spec/spec_helper.rb
         | 
| 56 59 | 
             
            homepage: https://github.com/gumroad/alterity
         | 
| 57 60 | 
             
            licenses:
         | 
| @@ -81,5 +84,7 @@ specification_version: 4 | |
| 81 84 | 
             
            summary: Execute your ActiveRecord migrations with Percona's pt-online-schema-change.
         | 
| 82 85 | 
             
            test_files:
         | 
| 83 86 | 
             
            - spec/alterity_spec.rb
         | 
| 87 | 
            +
            - spec/bin/custom_config.rb
         | 
| 84 88 | 
             
            - spec/bin/rails_app_migration_test.sh
         | 
| 89 | 
            +
            - spec/bin/test_custom_config_result.rb
         | 
| 85 90 | 
             
            - spec/spec_helper.rb
         |