Module: OnlineMigrations::Utils

Defined in:
lib/online_migrations/utils.rb

Class Method Summary collapse

Class Method Details

.ar_enumerate_columns_in_select_statementsObject



101
102
103
104
105
106
107
# File 'lib/online_migrations/utils.rb', line 101

def ar_enumerate_columns_in_select_statements
  if ar_version >= 7
    ActiveRecord::Base.enumerate_columns_in_select_statements
  else
    false
  end
end

.ar_partial_writes?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/online_migrations/utils.rb', line 89

def ar_partial_writes?
  ActiveRecord::Base.public_send(ar_partial_writes_setting)
end

.ar_partial_writes_settingObject



93
94
95
96
97
98
99
# File 'lib/online_migrations/utils.rb', line 93

def ar_partial_writes_setting
  if Utils.ar_version >= 7.0
    "partial_inserts"
  else
    "partial_writes"
  end
end

.ar_versionObject



9
10
11
# File 'lib/online_migrations/utils.rb', line 9

def ar_version
  ActiveRecord.version.to_s.to_f
end

.define_model(table_name) ⇒ Object



55
56
57
58
59
60
# File 'lib/online_migrations/utils.rb', line 55

def define_model(table_name)
  Class.new(ActiveRecord::Base) do
    self.table_name = table_name
    self.inheritance_column = :_type_disabled
  end
end

.developer_env?Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/online_migrations/utils.rb', line 22

def developer_env?
  env == "development" || env == "test"
end

.envObject



13
14
15
16
17
18
19
20
# File 'lib/online_migrations/utils.rb', line 13

def env
  if defined?(Rails.env)
    Rails.env
  else
    # default to production for safety
    ENV["RACK_ENV"] || "production"
  end
end

.estimated_count(connection, table_name) ⇒ Object

Returns estimated rows count for a table. www.citusdata.com/blog/2016/10/12/count-performance/



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/online_migrations/utils.rb', line 111

def estimated_count(connection, table_name)
  quoted_table = connection.quote(table_name)

  count = connection.select_value(<<~SQL)
    SELECT
      (reltuples / COALESCE(NULLIF(relpages, 0), 1)) *
      (pg_relation_size(#{quoted_table}) / (current_setting('block_size')::integer))
    FROM pg_catalog.pg_class
    WHERE relname = #{quoted_table}
      AND relnamespace = current_schema()::regnamespace
  SQL

  if count
    count = count.to_i
    # If the table has never yet been vacuumed or analyzed, reltuples contains -1
    # indicating that the row count is unknown.
    count = 0 if count < 0
    count
  end
end

.find_connection_class(model) ⇒ Object



154
155
156
157
158
159
# File 'lib/online_migrations/utils.rb', line 154

def find_connection_class(model)
  model.ancestors.find do |parent|
    parent == ActiveRecord::Base ||
      (parent.is_a?(Class) && parent.abstract_class?)
  end
end

.foreign_table_name(ref_name, options) ⇒ Object



66
67
68
69
70
# File 'lib/online_migrations/utils.rb', line 66

def foreign_table_name(ref_name, options)
  options.fetch(:to_table) do
    ActiveRecord::Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
  end
end

.index_name(table_name, column_name) ⇒ Object

Implementation is from ActiveRecord. This is not needed for ActiveRecord >= 7.1 (github.com/rails/rails/pull/47753).



74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/online_migrations/utils.rb', line 74

def index_name(table_name, column_name)
  max_index_name_size = 62
  name = "index_#{table_name}_on_#{Array(column_name) * '_and_'}"
  return name if name.bytesize <= max_index_name_size

  # Fallback to short version, add hash to ensure uniqueness
  hashed_identifier = "_#{OpenSSL::Digest::SHA256.hexdigest(name).first(10)}"
  name = "idx_on_#{Array(column_name) * '_'}"

  short_limit = max_index_name_size - hashed_identifier.bytesize
  short_name = name[0, short_limit]

  "#{short_name}#{hashed_identifier}"
end

.multiple_databases?Boolean

Returns:

  • (Boolean)


173
174
175
176
# File 'lib/online_migrations/utils.rb', line 173

def multiple_databases?
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: env)
  db_config.reject(&:replica?).size > 1
end

.raise_in_prod_or_say_in_dev(message) ⇒ Object



43
44
45
46
47
48
49
# File 'lib/online_migrations/utils.rb', line 43

def raise_in_prod_or_say_in_dev(message)
  if developer_env?
    say(message)
  else
    raise message
  end
end

.raise_or_say(message) ⇒ Object



35
36
37
38
39
40
41
# File 'lib/online_migrations/utils.rb', line 35

def raise_or_say(message)
  if developer_env? && !multiple_databases?
    raise message
  else
    say(message)
  end
end

.run_background_migrations_inline?Boolean

Returns:

  • (Boolean)


178
179
180
181
# File 'lib/online_migrations/utils.rb', line 178

def run_background_migrations_inline?
  run_inline = OnlineMigrations.config.run_background_migrations_inline
  run_inline && run_inline.call
end

.say(message) ⇒ Object



26
27
28
29
30
31
32
33
# File 'lib/online_migrations/utils.rb', line 26

def say(message)
  message = "[online_migrations] #{message}"
  if (migration = OnlineMigrations.current_migration)
    migration.say(message)
  elsif (logger = ActiveRecord::Base.logger)
    logger.info(message)
  end
end

.shard_names(model) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/online_migrations/utils.rb', line 161

def shard_names(model)
  model.ancestors.each do |ancestor|
    # There is no official method to get shard names from the model.
    # This is the way that currently is used in ActiveRecord tests themselves.
    pool_manager = ActiveRecord::Base.connection_handler.send(:get_pool_manager, ancestor.name)

    # .uniq call is not needed for Active Record 7.1+
    # See https://github.com/rails/rails/pull/49284.
    return pool_manager.shard_names.uniq if pool_manager
  end
end

.to_bool(value) ⇒ Object



62
63
64
# File 'lib/online_migrations/utils.rb', line 62

def to_bool(value)
  value.to_s.match?(/^true|t|yes|y|1|on$/i)
end

.volatile_default?(connection, type, value) ⇒ Boolean

Returns:

  • (Boolean)


135
136
137
138
139
140
141
142
# File 'lib/online_migrations/utils.rb', line 135

def volatile_default?(connection, type, value)
  return false if !(value.is_a?(Proc) || (type.to_s == "uuid" && value.is_a?(String)))

  value = value.call if value.is_a?(Proc)
  return false if !value.is_a?(String)

  value.scan(FUNCTION_CALL_RE).any? { |(function_name)| volatile_function?(connection, function_name.downcase) }
end

.volatile_function?(connection, function_name) ⇒ Boolean

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
152
# File 'lib/online_migrations/utils.rb', line 144

def volatile_function?(connection, function_name)
  query = <<~SQL
    SELECT provolatile
    FROM pg_catalog.pg_proc
    WHERE proname = #{connection.quote(function_name)}
  SQL

  connection.select_value(query) == "v"
end

.warn(message) ⇒ Object



51
52
53
# File 'lib/online_migrations/utils.rb', line 51

def warn(message)
  Kernel.warn("[online_migrations] #{message}")
end