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
31
32
# File 'lib/unreliable/build_order.rb', line 9

def build_order(arel)
  super(arel)

  adapter_name = Arel::Table.engine.connection.adapter_name
  return unless Unreliable::Config.enabled?
  return if distinct_on_postgres?(adapter_name)
  return if from_only_internal_metadata?(arel)
  return if from_one_table_with_ordered_pk?(arel)

  case 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/16/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

#distinct_on_postgres?(adapter_name) ⇒ Boolean

Returns:

  • (Boolean)


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

def distinct_on_postgres?(adapter_name)
  distinct_value && adapter_name == "PostgreSQL"
end

#from_one_table_with_ordered_pk?(arel) ⇒ Boolean

Returns:

  • (Boolean)


43
44
45
46
47
48
49
50
51
52
53
# File 'lib/unreliable/build_order.rb', line 43

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)


38
39
40
41
# File 'lib/unreliable/build_order.rb', line 38

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



63
64
65
66
67
68
69
70
71
# File 'lib/unreliable/build_order.rb', line 63

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



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

def primary_key_columns(arel)
  # primary_keys returns a String if it's one column, an Array if two or more.
  # Using the SchemaCache minimizes the number of times we have to, e.g. in MySQL,
  # SELECT column_name FROM information_schema.statistics
  # (or in Rails < 6, SELECT column_name FROM information_schema.key_column_usage)
  [ActiveRecord::Base.connection.schema_cache.primary_keys(arel.froms.first.name)].flatten
end