switchman 2.1.0 → 2.2.3
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/app/models/switchman/shard.rb +83 -128
- data/lib/switchman/active_record/attribute_methods.rb +39 -18
- data/lib/switchman/active_record/base.rb +11 -11
- data/lib/switchman/active_record/connection_handler.rb +1 -1
- data/lib/switchman/active_record/connection_pool.rb +3 -1
- data/lib/switchman/active_record/log_subscriber.rb +1 -4
- data/lib/switchman/active_record/migration.rb +6 -3
- data/lib/switchman/active_record/persistence.rb +10 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +1 -1
- data/lib/switchman/active_record/query_cache.rb +1 -1
- data/lib/switchman/active_record/query_methods.rb +23 -0
- data/lib/switchman/connection_pool_proxy.rb +1 -1
- data/lib/switchman/engine.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +3 -2
- metadata +11 -11
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c6658f02bb242697d5c6b9d7e9c57ba3613295fddc1de3d618cb74ed125835aa
         | 
| 4 | 
            +
              data.tar.gz: 05fa7e267a2d442d3f9862e041fe6f297e3272693dd15273bfb17ec86187450f
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 3651b8ad5d882571716507a49e264b90d70a318e436e816e9dbe2ec0b9ff70031f4bfa7fce4e04748bad8e4dad88a36d16eac4afa1b0c61e2f686ed138bb2ef5
         | 
| 7 | 
            +
              data.tar.gz: d1d98111fc70a2786768b61856ae4a54ba914275628489d99f12b7de7b0a891d62e7c41189aad2a388941a0c9ce89742d6751c4078ff15b57660d63d390978ce
         | 
| @@ -129,6 +129,11 @@ module Switchman | |
| 129 129 | 
             
                    cached_shards[id]
         | 
| 130 130 | 
             
                  end
         | 
| 131 131 |  | 
| 132 | 
            +
                  def preload_cache
         | 
| 133 | 
            +
                    cached_shards.reverse_merge!(active_shards.values.index_by(&:id))
         | 
| 134 | 
            +
                    cached_shards.reverse_merge!(all.index_by(&:id))
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 132 137 | 
             
                  def clear_cache
         | 
| 133 138 | 
             
                    cached_shards.clear
         | 
| 134 139 | 
             
                  end
         | 
| @@ -139,10 +144,9 @@ module Switchman | |
| 139 144 | 
             
                  # * +categories+ - an array of categories to activate
         | 
| 140 145 | 
             
                  # * +options+ -
         | 
| 141 146 | 
             
                  #    :parallel - true/false to execute in parallel, or a integer of how many
         | 
| 142 | 
            -
                  #                sub-processes | 
| 143 | 
            -
                  #                 | 
| 144 | 
            -
                  #                 | 
| 145 | 
            -
                  #    :max_procs - only run this many parallel processes at a time
         | 
| 147 | 
            +
                  #                sub-processes. Note that parallel invocation currently uses
         | 
| 148 | 
            +
                  #                forking, so should be used sparingly because you cannot get
         | 
| 149 | 
            +
                  #                results back
         | 
| 146 150 | 
             
                  #    :exception - :ignore, :raise, :defer (wait until the end and raise the first
         | 
| 147 151 | 
             
                  #                error), or a proc
         | 
| 148 152 | 
             
                  def with_each_shard(*args)
         | 
| @@ -163,11 +167,14 @@ module Switchman | |
| 163 167 | 
             
                      scope, categories = args
         | 
| 164 168 | 
             
                    end
         | 
| 165 169 |  | 
| 170 | 
            +
                    # back-compat
         | 
| 171 | 
            +
                    options[:parallel] = options.delete(:max_procs) if options.key?(:max_procs)
         | 
| 172 | 
            +
             | 
| 166 173 | 
             
                    parallel = case options[:parallel]
         | 
| 167 174 | 
             
                                 when true
         | 
| 168 | 
            -
                                    | 
| 175 | 
            +
                                   [Environment.cpu_count || 2, 2].min
         | 
| 169 176 | 
             
                                 when false, nil
         | 
| 170 | 
            -
                                    | 
| 177 | 
            +
                                   1
         | 
| 171 178 | 
             
                                 else
         | 
| 172 179 | 
             
                                   options[:parallel]
         | 
| 173 180 | 
             
                               end
         | 
| @@ -178,47 +185,19 @@ module Switchman | |
| 178 185 | 
             
                      scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
         | 
| 179 186 | 
             
                    end
         | 
| 180 187 |  | 
| 181 | 
            -
                    if parallel >  | 
| 182 | 
            -
                      max_procs = determine_max_procs(options.delete(:max_procs), parallel)
         | 
| 188 | 
            +
                    if parallel > 1
         | 
| 183 189 | 
             
                      if ::ActiveRecord::Relation === scope
         | 
| 184 190 | 
             
                        # still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
         | 
| 185 191 | 
             
                        database_servers = scope.reorder('database_server_id').select(:database_server_id).distinct.
         | 
| 186 192 | 
             
                            map(&:database_server).compact.uniq
         | 
| 187 193 | 
             
                        # nothing to do
         | 
| 188 194 | 
             
                        return if database_servers.count == 0
         | 
| 189 | 
            -
                        parallel = [(max_procs.to_f / database_servers.count).ceil, parallel].min if max_procs
         | 
| 190 195 |  | 
| 191 196 | 
             
                        scopes = Hash[database_servers.map do |server|
         | 
| 192 | 
            -
                           | 
| 193 | 
            -
                          if parallel == 1
         | 
| 194 | 
            -
                            subscopes = [server_scope]
         | 
| 195 | 
            -
                          else
         | 
| 196 | 
            -
                            subscopes = []
         | 
| 197 | 
            -
                            total = server_scope.count
         | 
| 198 | 
            -
                            ranges = []
         | 
| 199 | 
            -
                            server_scope.find_ids_in_ranges(:batch_size => (total.to_f / parallel).ceil) do |min, max|
         | 
| 200 | 
            -
                              ranges << [min, max]
         | 
| 201 | 
            -
                            end
         | 
| 202 | 
            -
                            # create a half-open range on the last one
         | 
| 203 | 
            -
                            ranges.last[1] = nil
         | 
| 204 | 
            -
                            ranges.each do |min, max|
         | 
| 205 | 
            -
                              subscope = server_scope.where("id>=?", min)
         | 
| 206 | 
            -
                              subscope = subscope.where("id<=?", max) if max
         | 
| 207 | 
            -
                              subscopes << subscope
         | 
| 208 | 
            -
                            end
         | 
| 209 | 
            -
                          end
         | 
| 210 | 
            -
                          [server, subscopes]
         | 
| 197 | 
            +
                          [server, server.shards.merge(scope)]
         | 
| 211 198 | 
             
                        end]
         | 
| 212 199 | 
             
                      else
         | 
| 213 200 | 
             
                        scopes = scope.group_by(&:database_server)
         | 
| 214 | 
            -
                        if parallel > 1
         | 
| 215 | 
            -
                          parallel = [(max_procs.to_f / scopes.count).ceil, parallel].min if max_procs
         | 
| 216 | 
            -
                          scopes = Hash[scopes.map do |(server, shards)|
         | 
| 217 | 
            -
                            [server, shards.in_groups(parallel, false).compact]
         | 
| 218 | 
            -
                          end]
         | 
| 219 | 
            -
                        else
         | 
| 220 | 
            -
                          scopes = Hash[scopes.map { |(server, shards)| [server, [shards]] }]
         | 
| 221 | 
            -
                        end
         | 
| 222 201 | 
             
                      end
         | 
| 223 202 |  | 
| 224 203 | 
             
                      exception_pipes = []
         | 
| @@ -244,8 +223,8 @@ module Switchman | |
| 244 223 | 
             
                      end
         | 
| 245 224 |  | 
| 246 225 | 
             
                      # only one process; don't bother forking
         | 
| 247 | 
            -
                      if scopes.length == 1 | 
| 248 | 
            -
                        return with_each_shard(scopes.first.last | 
| 226 | 
            +
                      if scopes.length == 1
         | 
| 227 | 
            +
                        return with_each_shard(scopes.first.last, categories, options) { yield }
         | 
| 249 228 | 
             
                      end
         | 
| 250 229 |  | 
| 251 230 | 
             
                      # clear connections prior to forking (no more queries will be executed in the parent,
         | 
| @@ -253,84 +232,78 @@ module Switchman | |
| 253 232 | 
             
                      # silly like dealloc'ing prepared statements)
         | 
| 254 233 | 
             
                      ::ActiveRecord::Base.clear_all_connections!
         | 
| 255 234 |  | 
| 256 | 
            -
                      scopes.each do |server,  | 
| 257 | 
            -
                         | 
| 258 | 
            -
             | 
| 259 | 
            -
             | 
| 260 | 
            -
             | 
| 261 | 
            -
             | 
| 262 | 
            -
                           | 
| 235 | 
            +
                      scopes.each do |server, subscope|
         | 
| 236 | 
            +
                        name = server.id
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                        exception_pipe = IO.pipe
         | 
| 239 | 
            +
                        exception_pipes << exception_pipe
         | 
| 240 | 
            +
                        pid, io_in, io_out, io_err = Open4.pfork4(lambda do
         | 
| 241 | 
            +
                          begin
         | 
| 242 | 
            +
                            Switchman.config[:on_fork_proc]&.call
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                            # set a pretty name for the process title, up to 128 characters
         | 
| 245 | 
            +
                            # (we don't actually know the limit, depending on how the process
         | 
| 246 | 
            +
                            # was started)
         | 
| 247 | 
            +
                            # first, simplify the binary name by stripping directories,
         | 
| 248 | 
            +
                            # then truncate arguments as necessary
         | 
| 249 | 
            +
                            bin = File.basename($0)  # Process.argv0 doesn't work on Ruby 2.5 (https://bugs.ruby-lang.org/issues/15887)
         | 
| 250 | 
            +
                            max_length = 128 - bin.length - name.length - 3
         | 
| 251 | 
            +
                            args = ARGV.join(" ")
         | 
| 252 | 
            +
                            if max_length >= 0
         | 
| 253 | 
            +
                              args = args[0..max_length]
         | 
| 254 | 
            +
                            end
         | 
| 255 | 
            +
                            new_title = [bin, args, name].join(" ")
         | 
| 256 | 
            +
                            Process.setproctitle(new_title)
         | 
| 263 257 |  | 
| 264 | 
            -
             | 
| 265 | 
            -
             | 
| 266 | 
            -
                           | 
| 258 | 
            +
                            with_each_shard(subscope, categories, options) { yield }
         | 
| 259 | 
            +
                            exception_pipe.last.close
         | 
| 260 | 
            +
                          rescue Exception => e
         | 
| 267 261 | 
             
                            begin
         | 
| 268 | 
            -
                               | 
| 269 | 
            -
             | 
| 270 | 
            -
             | 
| 271 | 
            -
                               | 
| 272 | 
            -
             | 
| 273 | 
            -
                              # first, simplify the binary name by stripping directories,
         | 
| 274 | 
            -
                              # then truncate arguments as necessary
         | 
| 275 | 
            -
                              bin = File.basename($0)  # Process.argv0 doesn't work on Ruby 2.5 (https://bugs.ruby-lang.org/issues/15887)
         | 
| 276 | 
            -
                              max_length = 128 - bin.length - name.length - 3
         | 
| 277 | 
            -
                              args = ARGV.join(" ")
         | 
| 278 | 
            -
                              if max_length >= 0
         | 
| 279 | 
            -
                                args = args[0..max_length]
         | 
| 280 | 
            -
                              end
         | 
| 281 | 
            -
                              new_title = [bin, args, name].join(" ")
         | 
| 282 | 
            -
                              Process.setproctitle(new_title)
         | 
| 283 | 
            -
             | 
| 284 | 
            -
                              with_each_shard(subscope, categories, options) { yield }
         | 
| 285 | 
            -
                              exception_pipe.last.close
         | 
| 286 | 
            -
                            rescue Exception => e
         | 
| 287 | 
            -
                              begin
         | 
| 288 | 
            -
                                dumped = Marshal.dump(e)
         | 
| 289 | 
            -
                                dumped = nil if dumped.length > 64 * 1024
         | 
| 290 | 
            -
                              rescue
         | 
| 291 | 
            -
                                dumped = nil
         | 
| 292 | 
            -
                              end
         | 
| 262 | 
            +
                              dumped = Marshal.dump(e)
         | 
| 263 | 
            +
                              dumped = nil if dumped.length > 64 * 1024
         | 
| 264 | 
            +
                            rescue
         | 
| 265 | 
            +
                              dumped = nil
         | 
| 266 | 
            +
                            end
         | 
| 293 267 |  | 
| 294 | 
            -
             | 
| 295 | 
            -
             | 
| 296 | 
            -
             | 
| 297 | 
            -
             | 
| 298 | 
            -
             | 
| 299 | 
            -
             | 
| 300 | 
            -
             | 
| 301 | 
            -
             | 
| 302 | 
            -
                                end
         | 
| 303 | 
            -
                                e2.set_backtrace(backtrace)
         | 
| 304 | 
            -
                                e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
         | 
| 305 | 
            -
                                dumped = Marshal.dump(e2)
         | 
| 268 | 
            +
                            if dumped.nil?
         | 
| 269 | 
            +
                              # couldn't dump the exception; create a copy with just
         | 
| 270 | 
            +
                              # the message and the backtrace
         | 
| 271 | 
            +
                              e2 = e.class.new(e.message)
         | 
| 272 | 
            +
                              backtrace = e.backtrace
         | 
| 273 | 
            +
                              # truncate excessively long backtraces
         | 
| 274 | 
            +
                              if backtrace.length > 50
         | 
| 275 | 
            +
                                backtrace = backtrace[0...25] + ['...'] + backtrace[-25..-1]
         | 
| 306 276 | 
             
                              end
         | 
| 307 | 
            -
             | 
| 308 | 
            -
                               | 
| 309 | 
            -
                               | 
| 310 | 
            -
                              exception_pipe.last.flush
         | 
| 311 | 
            -
                              exception_pipe.last.close
         | 
| 312 | 
            -
                              exit! 1
         | 
| 277 | 
            +
                              e2.set_backtrace(backtrace)
         | 
| 278 | 
            +
                              e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
         | 
| 279 | 
            +
                              dumped = Marshal.dump(e2)
         | 
| 313 280 | 
             
                            end
         | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 318 | 
            -
             | 
| 319 | 
            -
             | 
| 320 | 
            -
                           | 
| 321 | 
            -
             | 
| 322 | 
            -
             | 
| 323 | 
            -
             | 
| 324 | 
            -
             | 
| 325 | 
            -
             | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 328 | 
            -
             | 
| 329 | 
            -
             | 
| 330 | 
            -
             | 
| 331 | 
            -
             | 
| 332 | 
            -
             | 
| 281 | 
            +
             | 
| 282 | 
            +
                            exception_pipe.last.set_encoding(dumped.encoding)
         | 
| 283 | 
            +
                            exception_pipe.last.write(dumped)
         | 
| 284 | 
            +
                            exception_pipe.last.flush
         | 
| 285 | 
            +
                            exception_pipe.last.close
         | 
| 286 | 
            +
                            exit! 1
         | 
| 287 | 
            +
                          end
         | 
| 288 | 
            +
                        end)
         | 
| 289 | 
            +
                        exception_pipe.last.close
         | 
| 290 | 
            +
                        pids << pid
         | 
| 291 | 
            +
                        io_in.close # don't care about writing to stdin
         | 
| 292 | 
            +
                        out_fds << io_out
         | 
| 293 | 
            +
                        err_fds << io_err
         | 
| 294 | 
            +
                        pid_to_name_map[pid] = name
         | 
| 295 | 
            +
                        fd_to_name_map[io_out] = name
         | 
| 296 | 
            +
                        fd_to_name_map[io_err] = name
         | 
| 297 | 
            +
             | 
| 298 | 
            +
                        while pids.count >= parallel
         | 
| 299 | 
            +
                          while out_fds.count >= parallel
         | 
| 300 | 
            +
                            # wait for output if we've hit the parallel limit
         | 
| 301 | 
            +
                            wait_for_output.call(out_fds, err_fds, fd_to_name_map)
         | 
| 333 302 | 
             
                          end
         | 
| 303 | 
            +
                          # we've gotten all the output from one fd so wait for its child process to exit
         | 
| 304 | 
            +
                          found_pid, status = Process.wait2
         | 
| 305 | 
            +
                          pids.delete(found_pid)
         | 
| 306 | 
            +
                          errors << pid_to_name_map[found_pid] if status.exitstatus != 0
         | 
| 334 307 | 
             
                        end
         | 
| 335 308 | 
             
                      end
         | 
| 336 309 |  | 
| @@ -544,24 +517,6 @@ module Switchman | |
| 544 517 | 
             
                    shard || source_shard || Shard.current
         | 
| 545 518 | 
             
                  end
         | 
| 546 519 |  | 
| 547 | 
            -
                  # given the provided option, determines whether we need to (and whether
         | 
| 548 | 
            -
                  # it's possible) to determine a reasonable default.
         | 
| 549 | 
            -
                  def determine_max_procs(max_procs_input, parallel_input=2)
         | 
| 550 | 
            -
                    max_procs = nil
         | 
| 551 | 
            -
                    if max_procs_input
         | 
| 552 | 
            -
                      max_procs = max_procs_input.to_i
         | 
| 553 | 
            -
                      max_procs = nil if max_procs == 0
         | 
| 554 | 
            -
                    else
         | 
| 555 | 
            -
                      return 1 if parallel_input.nil? || parallel_input < 1
         | 
| 556 | 
            -
                      cpus = Environment.cpu_count
         | 
| 557 | 
            -
                      if cpus && cpus > 0
         | 
| 558 | 
            -
                        max_procs = cpus * parallel_input
         | 
| 559 | 
            -
                      end
         | 
| 560 | 
            -
                    end
         | 
| 561 | 
            -
             | 
| 562 | 
            -
                    return max_procs
         | 
| 563 | 
            -
                  end
         | 
| 564 | 
            -
             | 
| 565 520 | 
             
                  private
         | 
| 566 521 | 
             
                  # in-process caching
         | 
| 567 522 | 
             
                  def cached_shards
         | 
| @@ -38,11 +38,15 @@ module Switchman | |
| 38 38 | 
             
                    def define_method_global_attribute(attr_name)
         | 
| 39 39 | 
             
                      if sharded_column?(attr_name)
         | 
| 40 40 | 
             
                        generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
         | 
| 41 | 
            -
                          def  | 
| 41 | 
            +
                          def __temp_global_attribute__
         | 
| 42 | 
            +
                            raw_value = original_#{attr_name}
         | 
| 43 | 
            +
                            return nil if raw_value.nil?
         | 
| 44 | 
            +
                            return raw_value if raw_value > Shard::IDS_PER_SHARD
         | 
| 45 | 
            +
             | 
| 42 46 | 
             
                            Shard.global_id_for(original_#{attr_name}, shard)
         | 
| 43 47 | 
             
                          end
         | 
| 44 | 
            -
                          alias_method 'global_#{attr_name}', : | 
| 45 | 
            -
                          undef_method : | 
| 48 | 
            +
                          alias_method 'global_#{attr_name}', :__temp_global_attribute__
         | 
| 49 | 
            +
                          undef_method :__temp_global_attribute__
         | 
| 46 50 | 
             
                        RUBY
         | 
| 47 51 | 
             
                      else
         | 
| 48 52 | 
             
                        define_method_unsharded_column(attr_name, 'global')
         | 
| @@ -52,11 +56,13 @@ module Switchman | |
| 52 56 | 
             
                    def define_method_local_attribute(attr_name)
         | 
| 53 57 | 
             
                      if sharded_column?(attr_name)
         | 
| 54 58 | 
             
                        generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
         | 
| 55 | 
            -
                          def  | 
| 56 | 
            -
                             | 
| 59 | 
            +
                          def __temp_local_attribute__
         | 
| 60 | 
            +
                            raw_value = original_#{attr_name}
         | 
| 61 | 
            +
                            return nil if raw_value.nil?
         | 
| 62 | 
            +
                            return raw_value % Shard::IDS_PER_SHARD
         | 
| 57 63 | 
             
                          end
         | 
| 58 | 
            -
                          alias_method 'local_#{attr_name}', : | 
| 59 | 
            -
                          undef_method : | 
| 64 | 
            +
                          alias_method 'local_#{attr_name}', :__temp_local_attribute__
         | 
| 65 | 
            +
                          undef_method :__temp_local_attribute__
         | 
| 60 66 | 
             
                        RUBY
         | 
| 61 67 | 
             
                      else
         | 
| 62 68 | 
             
                        define_method_unsharded_column(attr_name, 'local')
         | 
| @@ -84,7 +90,7 @@ module Switchman | |
| 84 90 | 
             
                    def define_method_original_attribute(attr_name)
         | 
| 85 91 | 
             
                      if sharded_column?(attr_name)
         | 
| 86 92 | 
             
                        reflection = reflection_for_integer_attribute(attr_name)
         | 
| 87 | 
            -
                        if attr_name == "id" | 
| 93 | 
            +
                        if attr_name == "id"
         | 
| 88 94 | 
             
                          return if self.method_defined?(:original_id)
         | 
| 89 95 | 
             
                          owner = self
         | 
| 90 96 | 
             
                        else
         | 
| @@ -94,18 +100,33 @@ module Switchman | |
| 94 100 | 
             
                          # rename the original method to original_
         | 
| 95 101 | 
             
                          alias_method 'original_#{attr_name}', '#{attr_name}'
         | 
| 96 102 | 
             
                          # and replace with one that transposes the id
         | 
| 97 | 
            -
                          def  | 
| 98 | 
            -
                             | 
| 103 | 
            +
                          def __temp_relative_attribute__
         | 
| 104 | 
            +
                            raw_value = original_#{attr_name}
         | 
| 105 | 
            +
                            return nil if raw_value.nil?
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                            abs_raw_value = raw_value.abs
         | 
| 108 | 
            +
                            current_shard = Shard.current(#{shard_category_code_for_reflection(reflection)})
         | 
| 109 | 
            +
                            same_shard = shard == current_shard
         | 
| 110 | 
            +
                            return raw_value if same_shard && abs_raw_value < Shard::IDS_PER_SHARD
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                            value_shard_id = abs_raw_value / Shard::IDS_PER_SHARD
         | 
| 113 | 
            +
                            # this is a stupid case when someone stuffed a global id for the current shard in instead
         | 
| 114 | 
            +
                            # of a local id
         | 
| 115 | 
            +
                            return raw_value % Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
         | 
| 116 | 
            +
                            return raw_value if !same_shard && abs_raw_value > Shard::IDS_PER_SHARD
         | 
| 117 | 
            +
                            return shard.global_id_for(raw_value) if !same_shard && abs_raw_value < Shard::IDS_PER_SHARD
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                            Shard.relative_id_for(raw_value, shard, current_shard)
         | 
| 99 120 | 
             
                          end
         | 
| 100 | 
            -
                          alias_method '#{attr_name}', : | 
| 101 | 
            -
                          undef_method : | 
| 121 | 
            +
                          alias_method '#{attr_name}', :__temp_relative_attribute__
         | 
| 122 | 
            +
                          undef_method :__temp_relative_attribute__
         | 
| 102 123 |  | 
| 103 124 | 
             
                          alias_method 'original_#{attr_name}=', '#{attr_name}='
         | 
| 104 | 
            -
                          def  | 
| 125 | 
            +
                          def __temp_relative_attribute_assignment__(new_value)
         | 
| 105 126 | 
             
                            self.original_#{attr_name} = Shard.relative_id_for(new_value, Shard.current(#{shard_category_code_for_reflection(reflection)}), shard)
         | 
| 106 127 | 
             
                          end
         | 
| 107 | 
            -
                          alias_method '#{attr_name}=', : | 
| 108 | 
            -
                          undef_method : | 
| 128 | 
            +
                          alias_method '#{attr_name}=', :__temp_relative_attribute_assignment__
         | 
| 129 | 
            +
                          undef_method :__temp_relative_attribute_assignment__
         | 
| 109 130 | 
             
                        RUBY
         | 
| 110 131 | 
             
                      else
         | 
| 111 132 | 
             
                        define_method_unsharded_column(attr_name, 'global')
         | 
| @@ -115,11 +136,11 @@ module Switchman | |
| 115 136 | 
             
                    def define_method_unsharded_column(attr_name, prefix)
         | 
| 116 137 | 
             
                      return if columns_hash["#{prefix}_#{attr_name}"]
         | 
| 117 138 | 
             
                      generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
         | 
| 118 | 
            -
                          def  | 
| 139 | 
            +
                          def __temp_unsharded_attribute__
         | 
| 119 140 | 
             
                            raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
         | 
| 120 141 | 
             
                          end
         | 
| 121 | 
            -
                          alias_method '#{prefix}_#{attr_name}', : | 
| 122 | 
            -
                          undef_method : | 
| 142 | 
            +
                          alias_method '#{prefix}_#{attr_name}', :__temp_unsharded_attribute__
         | 
| 143 | 
            +
                          undef_method :__temp_unsharded_attribute__
         | 
| 123 144 | 
             
                      RUBY
         | 
| 124 145 | 
             
                    end
         | 
| 125 146 | 
             
                  end
         | 
| @@ -80,17 +80,17 @@ module Switchman | |
| 80 80 | 
             
                    end
         | 
| 81 81 | 
             
                  end
         | 
| 82 82 |  | 
| 83 | 
            -
                  def self. | 
| 84 | 
            -
                    klass. | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
                      end
         | 
| 83 | 
            +
                  def self.prepended(klass)
         | 
| 84 | 
            +
                    klass.singleton_class.prepend(ClassMethods)
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  def _run_initialize_callbacks
         | 
| 88 | 
            +
                    @shard ||= if self.class.sharded_primary_key?
         | 
| 89 | 
            +
                      Shard.shard_for(self[self.class.primary_key], Shard.current(self.class.shard_category))
         | 
| 90 | 
            +
                    else
         | 
| 91 | 
            +
                      Shard.current(self.class.shard_category)
         | 
| 93 92 | 
             
                    end
         | 
| 93 | 
            +
                    super
         | 
| 94 94 | 
             
                  end
         | 
| 95 95 |  | 
| 96 96 | 
             
                  def shard
         | 
| @@ -158,7 +158,7 @@ module Switchman | |
| 158 158 | 
             
                  end
         | 
| 159 159 |  | 
| 160 160 | 
             
                  def update_columns(*)
         | 
| 161 | 
            -
                    db =  | 
| 161 | 
            +
                    db = shard.database_server
         | 
| 162 162 | 
             
                    if ::GuardRail.environment != db.guard_rail_environment
         | 
| 163 163 | 
             
                      return db.unguard { super }
         | 
| 164 164 | 
             
                    else
         | 
| @@ -118,7 +118,7 @@ module Switchman | |
| 118 118 | 
             
                      next if k == spec_name
         | 
| 119 119 |  | 
| 120 120 | 
             
                      v = owner_to_pool[k]
         | 
| 121 | 
            -
                      owner_to_pool.delete(k) if v.is_a?(ConnectionPoolProxy) && v.spec.name == spec_name
         | 
| 121 | 
            +
                      owner_to_pool.delete(k) if v.is_a?(ConnectionPoolProxy) && v.default_pool.spec.name == spec_name
         | 
| 122 122 | 
             
                    end
         | 
| 123 123 |  | 
| 124 124 | 
             
                    # unwrap the pool from inside a ConnectionPoolProxy
         | 
| @@ -20,10 +20,7 @@ module Switchman | |
| 20 20 | 
             
                    shard = "  [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
         | 
| 21 21 |  | 
| 22 22 | 
             
                    unless (payload[:binds] || []).empty?
         | 
| 23 | 
            -
                       | 
| 24 | 
            -
                      args = use_old_format ?
         | 
| 25 | 
            -
                        [payload[:binds], payload[:type_casted_binds]] :
         | 
| 26 | 
            -
                        [payload[:type_casted_binds]]
         | 
| 23 | 
            +
                      args = [payload[:type_casted_binds]]
         | 
| 27 24 | 
             
                      casted_params = type_casted_binds(*args)
         | 
| 28 25 | 
             
                      binds = "  " + payload[:binds].zip(casted_params).map { |attr, value|
         | 
| 29 26 | 
             
                        render_bind(attr, value)
         | 
| @@ -30,10 +30,13 @@ module Switchman | |
| 30 30 | 
             
                end
         | 
| 31 31 |  | 
| 32 32 | 
             
                module Migrator
         | 
| 33 | 
            -
                  # significant change:  | 
| 33 | 
            +
                  # significant change: just return MIGRATOR_SALT directly
         | 
| 34 | 
            +
                  # especially if you're going through pgbouncer, the database
         | 
| 35 | 
            +
                  # name you're accessing may not be consistent. it is NOT allowed
         | 
| 36 | 
            +
                  # to run migrations against multiple shards in the same database
         | 
| 37 | 
            +
                  # concurrently
         | 
| 34 38 | 
             
                  def generate_migrator_advisory_lock_id
         | 
| 35 | 
            -
                     | 
| 36 | 
            -
                    ::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
         | 
| 39 | 
            +
                    ::ActiveRecord::Migrator::MIGRATOR_SALT
         | 
| 37 40 | 
             
                  end
         | 
| 38 41 |  | 
| 39 42 | 
             
                  if ::Rails.version >= '6.0'
         | 
| @@ -13,6 +13,15 @@ module Switchman | |
| 13 13 | 
             
                      shard.activate(self.class.shard_category) { super }
         | 
| 14 14 | 
             
                    end
         | 
| 15 15 | 
             
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def delete
         | 
| 18 | 
            +
                    db = shard.database_server
         | 
| 19 | 
            +
                    if ::GuardRail.environment != db.guard_rail_environment
         | 
| 20 | 
            +
                      return db.unguard { super }
         | 
| 21 | 
            +
                    else
         | 
| 22 | 
            +
                      super
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 16 25 | 
             
                end
         | 
| 17 26 | 
             
              end
         | 
| 18 | 
            -
            end
         | 
| 27 | 
            +
            end
         | 
| @@ -102,7 +102,7 @@ module Switchman | |
| 102 102 | 
             
                        WHERE i.relkind = 'i'
         | 
| 103 103 | 
             
                          AND d.indisprimary = 'f'
         | 
| 104 104 | 
             
                          AND t.relname = '#{table_name}'
         | 
| 105 | 
            -
                          AND  | 
| 105 | 
            +
                          AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
         | 
| 106 106 | 
             
                        ORDER BY i.relname
         | 
| 107 107 | 
             
                      SQL
         | 
| 108 108 |  | 
| @@ -19,7 +19,7 @@ module Switchman | |
| 19 19 | 
             
                              connection_id: object_id,
         | 
| 20 20 | 
             
                              cached: true
         | 
| 21 21 | 
             
                            }
         | 
| 22 | 
            -
                            args[:type_casted_binds] = -> { type_casted_binds(binds) } | 
| 22 | 
            +
                            args[:type_casted_binds] = -> { type_casted_binds(binds) }
         | 
| 23 23 | 
             
                            ::ActiveSupport::Notifications.instrument(
         | 
| 24 24 | 
             
                                "sql.active_record",
         | 
| 25 25 | 
             
                                args
         | 
| @@ -78,6 +78,10 @@ module Switchman | |
| 78 78 | 
             
                    end
         | 
| 79 79 | 
             
                  end
         | 
| 80 80 |  | 
| 81 | 
            +
                  def or(other)
         | 
| 82 | 
            +
                    super(other.shard(self.primary_shard))
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 81 85 | 
             
                  private
         | 
| 82 86 |  | 
| 83 87 | 
             
                  if ::Rails.version >= '5.2'
         | 
| @@ -258,6 +262,25 @@ module Switchman | |
| 258 262 | 
             
                        end)
         | 
| 259 263 | 
             
                      end
         | 
| 260 264 |  | 
| 265 | 
            +
                      if predicate.is_a?(::Arel::Nodes::Grouping)
         | 
| 266 | 
            +
                        next predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
         | 
| 267 | 
            +
             | 
| 268 | 
            +
                        or_expr = predicate.expr
         | 
| 269 | 
            +
                        left_node = or_expr.left
         | 
| 270 | 
            +
                        right_node = or_expr.right
         | 
| 271 | 
            +
                        new_left_predicates, binds = transpose_predicates([left_node], source_shard, target_shard,
         | 
| 272 | 
            +
                          remove_nonlocal_primary_keys,
         | 
| 273 | 
            +
                          binds: binds,
         | 
| 274 | 
            +
                          dup_binds_on_mutation: dup_binds_on_mutation)
         | 
| 275 | 
            +
                        new_right_predicates, binds = transpose_predicates([right_node], source_shard, target_shard,
         | 
| 276 | 
            +
                          remove_nonlocal_primary_keys,
         | 
| 277 | 
            +
                          binds: binds,
         | 
| 278 | 
            +
                          dup_binds_on_mutation: dup_binds_on_mutation)
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                        next predicate if new_left_predicates[0] == left_node && new_right_predicates[0] == right_node
         | 
| 281 | 
            +
                        next ::Arel::Nodes::Grouping.new ::Arel::Nodes::Or.new(new_left_predicates[0], new_right_predicates[0])
         | 
| 282 | 
            +
                      end
         | 
| 283 | 
            +
             | 
| 261 284 | 
             
                      next predicate unless predicate.is_a?(::Arel::Nodes::Binary)
         | 
| 262 285 | 
             
                      next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
         | 
| 263 286 | 
             
                      relation, column = relation_and_column(predicate.left)
         | 
    
        data/lib/switchman/engine.rb
    CHANGED
    
    | @@ -95,7 +95,7 @@ module Switchman | |
| 95 95 |  | 
| 96 96 | 
             
                    ::StandardError.include(StandardError)
         | 
| 97 97 |  | 
| 98 | 
            -
                     | 
| 98 | 
            +
                    prepend ActiveRecord::Base
         | 
| 99 99 | 
             
                    include ActiveRecord::AttributeMethods
         | 
| 100 100 | 
             
                    include ActiveRecord::Persistence
         | 
| 101 101 | 
             
                    singleton_class.prepend ActiveRecord::ModelSchema::ClassMethods
         | 
    
        data/lib/switchman/version.rb
    CHANGED
    
    
    
        data/lib/tasks/switchman.rake
    CHANGED
    
    | @@ -46,7 +46,8 @@ module Switchman | |
| 46 46 | 
             
                end
         | 
| 47 47 |  | 
| 48 48 | 
             
                def self.options
         | 
| 49 | 
            -
                   | 
| 49 | 
            +
                  # we still pass through both of these options for back-compat purposes
         | 
| 50 | 
            +
                  { parallel: ENV['PARALLEL']&.to_i, max_procs: ENV['MAX_PARALLEL_PROCS']&.to_i }
         | 
| 50 51 | 
             
                end
         | 
| 51 52 |  | 
| 52 53 | 
             
                # categories - an array or proc, to activate as the current shard during the
         | 
| @@ -89,7 +90,7 @@ module Switchman | |
| 89 90 | 
             
                            nil
         | 
| 90 91 | 
             
                          end
         | 
| 91 92 | 
             
                        rescue => e
         | 
| 92 | 
            -
                          puts "Exception from #{e.current_shard.id}: #{e.current_shard.description}" if options[:parallel] != 0
         | 
| 93 | 
            +
                          puts "Exception from #{e.current_shard.id}: #{e.current_shard.description}" if options[:parallel].to_i != 0
         | 
| 93 94 | 
             
                          raise
         | 
| 94 95 | 
             
                        end
         | 
| 95 96 | 
             
                      end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,16 +1,16 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: switchman
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.2.3
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Cody Cutrer
         | 
| 8 8 | 
             
            - James Williams
         | 
| 9 9 | 
             
            - Jacob Fugal
         | 
| 10 | 
            -
            autorequire:
         | 
| 10 | 
            +
            autorequire: 
         | 
| 11 11 | 
             
            bindir: bin
         | 
| 12 12 | 
             
            cert_chain: []
         | 
| 13 | 
            -
            date:  | 
| 13 | 
            +
            date: 2022-03-09 00:00:00.000000000 Z
         | 
| 14 14 | 
             
            dependencies:
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 16 16 | 
             
              name: railties
         | 
| @@ -18,7 +18,7 @@ dependencies: | |
| 18 18 | 
             
                requirements:
         | 
| 19 19 | 
             
                - - ">="
         | 
| 20 20 | 
             
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            -
                    version: '5. | 
| 21 | 
            +
                    version: '5.2'
         | 
| 22 22 | 
             
                - - "<"
         | 
| 23 23 | 
             
                  - !ruby/object:Gem::Version
         | 
| 24 24 | 
             
                    version: '6.1'
         | 
| @@ -28,7 +28,7 @@ dependencies: | |
| 28 28 | 
             
                requirements:
         | 
| 29 29 | 
             
                - - ">="
         | 
| 30 30 | 
             
                  - !ruby/object:Gem::Version
         | 
| 31 | 
            -
                    version: '5. | 
| 31 | 
            +
                    version: '5.2'
         | 
| 32 32 | 
             
                - - "<"
         | 
| 33 33 | 
             
                  - !ruby/object:Gem::Version
         | 
| 34 34 | 
             
                    version: '6.1'
         | 
| @@ -38,7 +38,7 @@ dependencies: | |
| 38 38 | 
             
                requirements:
         | 
| 39 39 | 
             
                - - ">="
         | 
| 40 40 | 
             
                  - !ruby/object:Gem::Version
         | 
| 41 | 
            -
                    version: '5. | 
| 41 | 
            +
                    version: '5.2'
         | 
| 42 42 | 
             
                - - "<"
         | 
| 43 43 | 
             
                  - !ruby/object:Gem::Version
         | 
| 44 44 | 
             
                    version: '6.1'
         | 
| @@ -48,7 +48,7 @@ dependencies: | |
| 48 48 | 
             
                requirements:
         | 
| 49 49 | 
             
                - - ">="
         | 
| 50 50 | 
             
                  - !ruby/object:Gem::Version
         | 
| 51 | 
            -
                    version: '5. | 
| 51 | 
            +
                    version: '5.2'
         | 
| 52 52 | 
             
                - - "<"
         | 
| 53 53 | 
             
                  - !ruby/object:Gem::Version
         | 
| 54 54 | 
             
                    version: '6.1'
         | 
| @@ -257,7 +257,7 @@ homepage: http://www.instructure.com/ | |
| 257 257 | 
             
            licenses:
         | 
| 258 258 | 
             
            - MIT
         | 
| 259 259 | 
             
            metadata: {}
         | 
| 260 | 
            -
            post_install_message:
         | 
| 260 | 
            +
            post_install_message: 
         | 
| 261 261 | 
             
            rdoc_options: []
         | 
| 262 262 | 
             
            require_paths:
         | 
| 263 263 | 
             
            - lib
         | 
| @@ -265,15 +265,15 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 265 265 | 
             
              requirements:
         | 
| 266 266 | 
             
              - - ">="
         | 
| 267 267 | 
             
                - !ruby/object:Gem::Version
         | 
| 268 | 
            -
                  version: '2. | 
| 268 | 
            +
                  version: '2.6'
         | 
| 269 269 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 270 270 | 
             
              requirements:
         | 
| 271 271 | 
             
              - - ">="
         | 
| 272 272 | 
             
                - !ruby/object:Gem::Version
         | 
| 273 273 | 
             
                  version: '0'
         | 
| 274 274 | 
             
            requirements: []
         | 
| 275 | 
            -
            rubygems_version: 3. | 
| 276 | 
            -
            signing_key:
         | 
| 275 | 
            +
            rubygems_version: 3.1.4
         | 
| 276 | 
            +
            signing_key: 
         | 
| 277 277 | 
             
            specification_version: 4
         | 
| 278 278 | 
             
            summary: Rails sharding magic
         | 
| 279 279 | 
             
            test_files: []
         |