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



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/switchman/active_record/query_methods.rb', line 66

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

#primary_shardObject

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



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

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

#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:

  • (::ActiveRecord::ImmutableRelation)


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

def shard_source_value=(value)
  raise ::ActiveRecord::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:

  • (::ActiveRecord::ImmutableRelation)


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

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

#transpose_predicate_value(value, current_shard, target_shard, attribute_type, remove_non_local_ids) ⇒ Object



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/switchman/active_record/query_methods.rb', line 331

def transpose_predicate_value(value, current_shard, target_shard, attribute_type, remove_non_local_ids)
  if value.is_a?(::Arel::Nodes::BindParam)
    query_att = value.value
    current_id = query_att.value_before_type_cast
    if current_id.is_a?(::ActiveRecord::StatementCache::Substitute)
      current_id.sharded = true # mark for transposition later
      current_id.primary = true if attribute_type == :primary
      value
    else
      local_id = Shard.relative_id_for(current_id, current_shard, target_shard) || current_id
      local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
      if current_id != local_id
        # make a new bind param
        ::Arel::Nodes::BindParam.new(query_att.class.new(query_att.name, local_id, query_att.type))
      else
        value
      end
    end
  else
    local_id = Shard.relative_id_for(value, current_shard, target_shard) || value
    local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
    local_id
  end
end

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



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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
# File 'lib/switchman/active_record/query_methods.rb', line 236

def transpose_predicates(predicates,
                         source_shard,
                         target_shard,
                         remove_nonlocal_primary_keys = false,
                         binds: nil,
                         dup_binds_on_mutation: false)
  result = 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 type == :primary
          Shard.current(klass.shard_category)
        elsif type == :foreign
          source_shard_for_foreign_key(relation, column)
        end

    if ::Rails.version >= "5.2"
      new_right_value =
        case predicate.right
        when Array
          predicate.right.map {|val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove) }
        else
          transpose_predicate_value(predicate.right, current_source_shard, target_shard, type, remove)
        end
    else
      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 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 binds && bind = binds.detect{|b| b&.name.to_s == predicate.left.name.to_s}
          # before we mutate, dup
          if dup_binds_on_mutation
            binds = binds.map(&:dup)
            dup_binds_on_mutation = false
            bind = binds.find { |b| b&.name.to_s == predicate.left.name.to_s }
          end
          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
        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?(Integer) && local_id > Shard::IDS_PER_SHARD
        local_id
      end
    end
    if new_right_value == predicate.right
      predicate
    elsif 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
  result = [result, binds]
  result
end