Module: Switchman::ActiveRecord::QueryMethods

Defined in:
lib/switchman/active_record/query_methods.rb

Instance Method Summary collapse

Instance Method Details

#all_shardsObject

the shard value as an array or a relation



94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/switchman/active_record/query_methods.rb', line 94

def all_shards
  case shard_value
  when Shard, DefaultShard
    [shard_value]
  when ::ActiveRecord::Base
    shard_value.respond_to?(:associated_shards) ? shard_value.associated_shards : [shard_value.shard]
  when nil
    [Shard.current(klass.shard_category)]
  else
    shard_value
  end
end

#build_where(opts, other = []) ⇒ Object

moved to WhereClauseFactory#build in Rails 5



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/switchman/active_record/query_methods.rb', line 47

def build_where(opts, other = [])
  case opts
  when String, Array
    values = Hash === other.first ? other.first.values : other

    values.grep(ActiveRecord::Relation) do |rel|
      # serialize subqueries against the same shard as the outer query is currently
      # targeted to run against
      if rel.shard_source_value == :implicit && rel.primary_shard != primary_shard
        rel.shard!(primary_shard)
      end
      self.bind_values += rel.bind_values if ::Rails.version < '4.2'
    end

    [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
  when Hash, ::Arel::Nodes::Node
    predicates = super
    infer_shards_from_primary_key(predicates) if shard_source_value == :implicit && shard_value.is_a?(Shard)
    predicates = transpose_predicates(predicates, nil, primary_shard)
    predicates
  else
    super
  end
end

#primary_shardObject

the shard that where_values are relative to. if it’s multiple shards, they’re stored relative to the first shard



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/switchman/active_record/query_methods.rb', line 75

def primary_shard
  case shard_value
  when Shard, DefaultShard
    shard_value
  # associated_shards
  when ::ActiveRecord::Base
    shard_value.shard
  when Array
    shard_value.first
  when ::ActiveRecord::Relation
    Shard.default
  when nil
    Shard.current(klass.shard_category)
  else
    raise ArgumentError, "invalid shard value #{shard_value}"
  end
end

#reverse_order!Object

fixes an issue in Rails 4.2 with ‘reverse_sql_order` and qualified names where quoted_table_name is called before shard(s) have been activated if there’s no ordering



111
112
113
114
115
116
117
118
119
120
# File 'lib/switchman/active_record/query_methods.rb', line 111

def reverse_order!
  orders = order_values.uniq
  orders.reject!(&:blank?)
  if orders.empty?
    self.order_values = [arel_table[primary_key].desc]
  else
    self.order_values = reverse_sql_order(orders)
  end
  self
end

#shard(value, source = :explicit) ⇒ Object



30
31
32
# File 'lib/switchman/active_record/query_methods.rb', line 30

def shard(value, source = :explicit)
  spawn.shard!(value, source)
end

#shard!(value, source = :explicit) ⇒ Object

Raises:

  • (ArgumentError)


34
35
36
37
38
39
40
41
42
43
# File 'lib/switchman/active_record/query_methods.rb', line 34

def shard!(value, source = :explicit)
  raise ArgumentError, "shard can't be nil" unless value
  old_primary_shard = self.primary_shard
  self.shard_value = value
  self.shard_source_value = source
  if (old_primary_shard != self.primary_shard || source == :to_a)
    transpose_clauses(old_primary_shard, self.primary_shard, source == :to_a)
  end
  self
end

#shard_source_valueObject



18
19
20
# File 'lib/switchman/active_record/query_methods.rb', line 18

def shard_source_value
  @values[:shard_source]
end

#shard_source_value=(value) ⇒ Object

Raises:

  • (ImmutableRelation)


25
26
27
28
# File 'lib/switchman/active_record/query_methods.rb', line 25

def shard_source_value=(value)
  raise ImmutableRelation if @loaded
  @values[:shard_source] = value
end

#shard_valueObject

shard_value is one of:

A shard
An array or relation of shards
An AR object (query runs against that object's associated_shards)

shard_source_value is one of:

:implicit    - inferred from current shard when relation was created, or primary key where clause
:explicit    - explicit set on the relation
:association - a special value that scopes from associations use to use slightly different logic
               for foreign key transposition
:to_a        - a special value that Relation#to_a uses when querying multiple shards to
               remove primary keys from conditions that aren't applicable to the current shard


15
16
17
# File 'lib/switchman/active_record/query_methods.rb', line 15

def shard_value
  @values[:shard]
end

#shard_value=(value) ⇒ Object

Raises:

  • (ImmutableRelation)


21
22
23
24
# File 'lib/switchman/active_record/query_methods.rb', line 21

def shard_value=(value)
  raise ImmutableRelation if @loaded
  @values[:shard] = value
end

#transpose_predicates(predicates, source_shard, target_shard, remove_nonlocal_primary_keys = false, binds = nil) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/switchman/active_record/query_methods.rb', line 272

def transpose_predicates(predicates, source_shard, target_shard, remove_nonlocal_primary_keys = false, binds = nil)
  predicates.map do |predicate|
    next predicate unless predicate.is_a?(::Arel::Nodes::Binary)
    next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
    relation, column = relation_and_column(predicate.left)
    next predicate unless (type = transposable_attribute_type(relation, column))

    remove = true if type == :primary &&
        remove_nonlocal_primary_keys &&
        predicate.left.relation.model == klass &&
        predicate.is_a?(::Arel::Nodes::Equality)

    current_source_shard =
        if source_shard
          source_shard
        elsif shard_source_value == :explicit
          primary_shard
        elsif type == :primary
          Shard.current(klass.shard_category)
        elsif type == :foreign
          source_shard_for_foreign_key(relation, column)
        end

    new_right_value = case predicate.right
    when Array
      local_ids = []
      predicate.right.each do |value|
        local_id = Shard.relative_id_for(value, current_source_shard, target_shard)
        next unless local_id
        unless remove && local_id > Shard::IDS_PER_SHARD
          if ::Rails.version > "4.2" && value.is_a?(::Arel::Nodes::Casted)
            if local_id == value.val
              local_id = value
            elsif local_id != value
              local_id = value.class.new(local_id, value.attribute)
            end
          end
          local_ids << local_id
        end
      end
      local_ids
    when ::Arel::Nodes::BindParam
      # look for a bind param with a matching column name
      if ::Rails.version >= "5"
        binds ||= where_clause.binds + having_clause.binds
        if binds && bind = binds.detect{|b| b.try(:name).to_s == predicate.left.name.to_s}
          if bind.value.is_a?(::ActiveRecord::StatementCache::Substitute)
            bind.value.sharded = true # mark for transposition later
            bind.value.primary = true if type == :primary
          else
            local_id = Shard.relative_id_for(bind.value, current_source_shard, target_shard)
            local_id = [] if remove && local_id > Shard::IDS_PER_SHARD
            bind.instance_variable_set(:@value, local_id)
            bind.instance_variable_set(:@value_for_database, nil)
          end
        end
      else
        if bind_values && idx = bind_values.find_index{|b| b.is_a?(Array) && b.first.try(:name).to_s == predicate.left.name.to_s}
          column, value = bind_values[idx]
          if ::Rails.version >= '4.2' && value.is_a?(::ActiveRecord::StatementCache::Substitute)
            value.sharded = true # mark for transposition later
            value.primary = true if type == :primary
          else
            local_id = Shard.relative_id_for(value, current_source_shard, target_shard)
            local_id = [] if remove && local_id > Shard::IDS_PER_SHARD
            bind_values[idx] = [column, local_id]
          end
        end
      end
      predicate.right
    else
      local_id = Shard.relative_id_for(predicate.right, current_source_shard, target_shard) || predicate.right
      local_id = [] if remove && local_id.is_a?(Fixnum) && local_id > Shard::IDS_PER_SHARD
      local_id
    end

    if new_right_value == predicate.right
      predicate
    elsif ::Rails.version >= "4.2" && predicate.right.is_a?(::Arel::Nodes::Casted)
      if new_right_value == predicate.right.val
        predicate
      else
        predicate.class.new(predicate.left, predicate.right.class.new(new_right_value, predicate.right.attribute))
      end
    else
      predicate.class.new(predicate.left, new_right_value)
    end
  end
end