module Switchman::ActiveRecord::Relation

Public Class Methods

new(*, **) click to toggle source
Calls superclass method
# File lib/switchman/active_record/relation.rb, line 10
def initialize(*, **)
  super
  self.shard_value = Shard.current(klass ? klass.connection_classes : :primary) unless shard_value
  self.shard_source_value = :implicit unless shard_source_value
end
prepended(klass) click to toggle source
# File lib/switchman/active_record/relation.rb, line 6
def self.prepended(klass)
  klass::SINGLE_VALUE_METHODS.concat %i[shard shard_source]
end

Public Instance Methods

activate(unordered: false) { |self, first| ... } click to toggle source
# File lib/switchman/active_record/relation.rb, line 106
def activate(unordered: false, &block)
  shards = all_shards
  if Array === shards && shards.length == 1
    if shards.first == DefaultShard || shards.first == Shard.current(klass.connection_classes)
      yield(self, shards.first)
    else
      shards.first.activate(klass.connection_classes) { yield(self, shards.first) }
    end
  else
    result_count = 0
    can_order = false
    result = Shard.with_each_shard(shards, [klass.connection_classes]) do
      # don't even query other shards if we're already past the limit
      next if limit_value && result_count >= limit_value && order_values.empty?

      relation = shard(Shard.current(klass.connection_classes), :to_a)
      # do a minimal query if possible
      relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?

      shard_results = relation.activate(&block)

      if shard_results.present? && !unordered
        can_order ||= can_order_cross_shard_results? unless order_values.empty?
        raise OrderOnMultiShardQuery if !can_order && !order_values.empty? && result_count.positive?

        result_count += shard_results.is_a?(Array) ? shard_results.length : 1
      end
      shard_results
    end

    result = reorder_cross_shard_results(result) if can_order
    result.slice!(limit_value..-1) if limit_value
    result
  end
end
can_order_cross_shard_results?() click to toggle source
# File lib/switchman/active_record/relation.rb, line 142
def can_order_cross_shard_results?
  # we only presume to be able to post-sort the most basic of orderings
  order_values.all? { |ov| ov.is_a?(::Arel::Nodes::Ordering) && ov.expr.is_a?(::Arel::Attributes::Attribute) }
end
clone() click to toggle source
Calls superclass method
# File lib/switchman/active_record/relation.rb, line 16
def clone
  result = super
  result.shard_value = Shard.current(klass ? klass.connection_classes : :primary) unless shard_value
  result
end
create(*, &block) click to toggle source
Calls superclass method
# File lib/switchman/active_record/relation.rb, line 35
def create(*, &block)
  primary_shard.activate(klass.connection_classes) { super }
end
create!(*, &block) click to toggle source
Calls superclass method
# File lib/switchman/active_record/relation.rb, line 39
def create!(*, &block)
  primary_shard.activate(klass.connection_classes) { super }
end
explain() click to toggle source
# File lib/switchman/active_record/relation.rb, line 47
def explain
  activate { |relation| relation.call_super(:explain, Relation) }
end
find_ids_in_ranges(options = {}) { |*ids| ... } click to toggle source
# File lib/switchman/active_record/relation.rb, line 73
def find_ids_in_ranges(options = {})
  is_integer = columns_hash[primary_key.to_s].type == :integer
  loose_mode = options[:loose] && is_integer
  # loose_mode: if we don't care about getting exactly batch_size ids in between
  # don't get the max - just get the min and add batch_size so we get that many _at most_
  values = loose_mode ? 'MIN(id)' : 'MIN(id), MAX(id)'

  batch_size = options[:batch_size].try(:to_i) || 1000
  quoted_primary_key = "#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
  as_id = ' AS id' unless primary_key == 'id'
  subquery_scope = except(:select).select("#{quoted_primary_key}#{as_id}").reorder(primary_key.to_sym).limit(loose_mode ? 1 : batch_size)
  subquery_scope = subquery_scope.where("#{quoted_primary_key} <= ?", options[:end_at]) if options[:end_at]

  first_subquery_scope = if options[:start_at]
                           subquery_scope.where("#{quoted_primary_key} >= ?",
                                                options[:start_at])
                         else
                           subquery_scope
                         end

  ids = connection.select_rows("SELECT #{values} FROM (#{first_subquery_scope.to_sql}) AS subquery").first

  while ids.first.present?
    ids.map!(&:to_i) if is_integer
    ids << ids.first + batch_size if loose_mode

    yield(*ids)
    last_value = ids.last
    next_subquery_scope = subquery_scope.where(["#{quoted_primary_key}>?", last_value])
    ids = connection.select_rows("SELECT #{values} FROM (#{next_subquery_scope.to_sql}) AS subquery").first
  end
end
merge(*) click to toggle source
Calls superclass method
# File lib/switchman/active_record/relation.rb, line 22
def merge(*)
  relation = super
  if relation.shard_value != shard_value && relation.shard_source_value == :implicit
    relation.shard_value = shard_value
    relation.shard_source_value = shard_source_value
  end
  relation
end
new(*, &block) click to toggle source
Calls superclass method
# File lib/switchman/active_record/relation.rb, line 31
def new(*, &block)
  primary_shard.activate(klass.connection_classes) { super }
end
records() click to toggle source
# File lib/switchman/active_record/relation.rb, line 51
def records
  return @records if loaded?

  results = activate { |relation| relation.call_super(:records, Relation) }
  case shard_value
  when Array, ::ActiveRecord::Relation, ::ActiveRecord::Base
    @records = results
    @loaded = true
  end
  results
end
reorder_cross_shard_results(results) click to toggle source
# File lib/switchman/active_record/relation.rb, line 147
def reorder_cross_shard_results(results)
  results.sort! do |l, r|
    result = 0
    order_values.each do |ov|
      if l.is_a?(::ActiveRecord::Base)
        a = l.attribute(ov.expr.name)
        b = r.attribute(ov.expr.name)
      else
        a, b = l, r
      end
      next if a == b

      if a.nil? || b.nil?
        result = 1 if a.nil?
        result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
      else
        result = a <=> b
      end

      result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
      break unless result.zero?
    end
    result
  end
end
to_sql() click to toggle source
Calls superclass method
# File lib/switchman/active_record/relation.rb, line 43
def to_sql
  primary_shard.activate(klass.connection_classes) { super }
end