1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
# frozen_string_literal: true
require "active_record/scoping/default"
require "active_record/scoping/named"
module ActiveRecord
# This class is used to create a table that keeps track of values and keys such
# as which environment migrations were run in.
#
# This is enabled by default. To disable this functionality set
# `use_metadata_table` to false in your database configuration.
class InternalMetadata # :nodoc:
class NullInternalMetadata # :nodoc:
end
attr_reader :arel_table
def initialize(pool)
@pool = pool
@arel_table = Arel::Table.new(table_name)
end
def primary_key
"key"
end
def value_key
"value"
end
def table_name
"#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
end
def enabled?
@pool.db_config.use_metadata_table?
end
def []=(key, value)
return unless enabled?
@pool.with_connection do |connection|
update_or_create_entry(connection, key, value)
end
end
def [](key)
return unless enabled?
@pool.with_connection do |connection|
if entry = select_entry(connection, key)
entry[value_key]
end
end
end
def delete_all_entries
dm = Arel::DeleteManager.new(arel_table)
@pool.with_connection do |connection|
connection.delete(dm, "#{self.class} Destroy")
end
end
def count
sm = Arel::SelectManager.new(arel_table)
sm.project(*Arel::Nodes::Count.new([Arel.star]))
@pool.with_connection do |connection|
connection.select_values(sm, "#{self.class} Count").first
end
end
def create_table_and_set_flags(environment, schema_sha1 = nil)
return unless enabled?
@pool.with_connection do |connection|
create_table
update_or_create_entry(connection, :environment, environment)
update_or_create_entry(connection, :schema_sha1, schema_sha1) if schema_sha1
end
end
# Creates an internal metadata table with columns +key+ and +value+
def create_table
return unless enabled?
@pool.with_connection do |connection|
unless connection.table_exists?(table_name)
connection.create_table(table_name, id: false) do |t|
t.string :key, **connection.internal_string_options_for_primary_key
t.string :value
t.timestamps
end
end
end
end
def drop_table
return unless enabled?
@pool.with_connection do |connection|
connection.drop_table table_name, if_exists: true
end
end
def table_exists?
@pool.schema_cache.data_source_exists?(table_name)
end
private
def update_or_create_entry(connection, key, value)
entry = select_entry(connection, key)
if entry
if entry[value_key] != value
update_entry(connection, key, value)
else
entry[value_key]
end
else
create_entry(connection, key, value)
end
end
def current_time(connection)
connection.default_timezone == :utc ? Time.now.utc : Time.now
end
def create_entry(connection, key, value)
im = Arel::InsertManager.new(arel_table)
im.insert [
[arel_table[primary_key], key],
[arel_table[value_key], value],
[arel_table[:created_at], current_time(connection)],
[arel_table[:updated_at], current_time(connection)]
]
connection.insert(im, "#{self.class} Create", primary_key, key)
end
def update_entry(connection, key, new_value)
um = Arel::UpdateManager.new(arel_table)
um.set [
[arel_table[value_key], new_value],
[arel_table[:updated_at], current_time(connection)]
]
um.where(arel_table[primary_key].eq(key))
connection.update(um, "#{self.class} Update")
end
def select_entry(connection, key)
sm = Arel::SelectManager.new(arel_table)
sm.project(Arel::Nodes::SqlLiteral.new("*", retryable: true))
sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
sm.order(arel_table[primary_key].asc)
sm.limit = 1
connection.select_all(sm, "#{self.class} Load").first
end
end
end
|