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



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

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.respond_to?(:shard_category) ? klass.shard_category : default)]
  else
    shard_value
  end
end

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



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/switchman/active_record/query_methods.rb', line 46

def build_where(opts, other = [])
  case opts
  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) if shard_source_value != :explicit
    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



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/switchman/active_record/query_methods.rb', line 60

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.respond_to?(:shard_category) ? klass.shard_category : :default)
  else
    raise ArgumentError, "invalid shard value #{shard_value}"
  end
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
44
# File 'lib/switchman/active_record/query_methods.rb', line 34

def shard!(value, source = :explicit)
  raise ArgumentError, "shard can't be nil" unless value
  primary_shard = self.primary_shard
  self.shard_value = value
  self.shard_source_value = source
  if (primary_shard != self.primary_shard || source == :to_a)
    self.where_values = transpose_predicates(where_values, primary_shard, self.primary_shard, source == :to_a) if !where_values.empty?
    self.having_values = transpose_predicates(having_values, primary_shard, self.primary_shard, source == :to_a) if !having_values.empty?
  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) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/switchman/active_record/query_methods.rb', line 185

def transpose_predicates(predicates, source_shard, target_shard, remove_nonlocal_primary_keys = false)
  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.engine == klass
    current_source_shard =
        if source_shard
          source_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)
        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 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
      predicate.right
    else
      local_id = Shard.relative_id_for(predicate.right, current_source_shard, target_shard)
      local_id = [] if remove && 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