Module: Unreliable::BuildOrder

Defined in:
lib/unreliable/build_order.rb

Instance Method Summary collapse

Instance Method Details

#build_order(arel) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/unreliable/build_order.rb', line 9

def build_order(arel)
  super(arel)

  return unless Unreliable::Config.enabled?
  return if from_only_internal_metadata?(arel)
  return if from_one_table_with_ordered_pk?(arel)

  case Arel::Table.engine.connection.adapter_name
  when "Mysql2"
    # https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_rand
    arel.order("RAND()")

  when "PostgreSQL", "SQLite"
    # https://www.postgresql.org/docs/13/functions-math.html#FUNCTIONS-MATH-RANDOM-TABLE
    # https://www.sqlite.org/lang_corefunc.html#random
    arel.order("RANDOM()")

  else
    raise ArgumentError, "unknown Arel::Table.engine"

  end
end

#from_one_table_with_ordered_pk?(arel) ⇒ Boolean

Returns:

  • (Boolean)


37
38
39
40
41
42
43
44
45
46
47
# File 'lib/unreliable/build_order.rb', line 37

def from_one_table_with_ordered_pk?(arel)
  # This gem isn't (yet) capable of determining if ordering is reliable when two or
  # more tables are being joined.
  return false if arel.ast.cores.first.source.is_a?(Arel::Nodes::JoinSource) &&
    arel.ast.cores.first.source.right.present?
  return false if arel.froms.count > 1

  # If the single table's primary key's column(s) are covered by the order columns,
  # return true and don't randomize the order.
  (primary_key_columns(arel) - order_columns(arel)).empty?
end

#from_only_internal_metadata?(arel) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
35
# File 'lib/unreliable/build_order.rb', line 32

def from_only_internal_metadata?(arel)
  # No need to randomize queries on ar_internal_metadata
  arel.froms.map(&:name) == [ActiveRecord::Base.]
end

#order_columns(arel) ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'lib/unreliable/build_order.rb', line 54

def order_columns(arel)
  from_table_name = arel.froms.first.name
  arel.orders
    .select { |order| order.is_a? Arel::Nodes::Ordering } # Don't try to parse textual orders
    .map(&:expr)
    .select { |expr| expr.relation.name == from_table_name }
    .map(&:name)
    .map(&:to_s) # In Rails < 5.2, the order column names are symbols; >= 5.2, strings
end

#primary_key_columns(arel) ⇒ Object



49
50
51
52
# File 'lib/unreliable/build_order.rb', line 49

def primary_key_columns(arel)
  # primary_key returns a String if it's one column, an Array if two or more
  [ActiveRecord::Base.connection.primary_key(arel.froms.first.name)].flatten
end