Merge pull request #38432 from kytrinyx/schema-cache-serialization-strategy-2
Expose Marshal as a legit SchemaCache serialization strategy
This commit is contained in:
commit
40c7c5e991
@ -3,6 +3,13 @@
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
class SchemaCache
|
||||
def self.load_from(filename)
|
||||
return unless File.file?(filename)
|
||||
|
||||
file = File.read(filename)
|
||||
filename.end_with?(".dump") ? Marshal.load(file) : YAML.load(file)
|
||||
end
|
||||
|
||||
attr_reader :version
|
||||
attr_accessor :connection
|
||||
|
||||
@ -129,6 +136,18 @@ def clear_data_source_cache!(name)
|
||||
@indexes.delete name
|
||||
end
|
||||
|
||||
def dump_to(filename)
|
||||
clear!
|
||||
connection.data_sources.each { |table| add(table) }
|
||||
open(filename, "wb") { |f|
|
||||
if filename.end_with?(".dump")
|
||||
f.write(Marshal.dump(self))
|
||||
else
|
||||
f.write(YAML.dump(self))
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def marshal_dump
|
||||
# if we get current version during initialization, it happens stack over flow.
|
||||
@version = connection.migration_context.current_version
|
||||
|
@ -133,24 +133,23 @@ class Railtie < Rails::Railtie # :nodoc:
|
||||
env_name: Rails.env,
|
||||
spec_name: "primary",
|
||||
)
|
||||
|
||||
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
|
||||
"primary",
|
||||
schema_cache_path: db_config&.schema_cache_path,
|
||||
)
|
||||
|
||||
if File.file?(filename)
|
||||
current_version = ActiveRecord::Migrator.current_version
|
||||
cache = ActiveRecord::ConnectionAdapters::SchemaCache.load_from(filename)
|
||||
next if cache.nil?
|
||||
|
||||
next if current_version.nil?
|
||||
current_version = ActiveRecord::Migrator.current_version
|
||||
next if current_version.nil?
|
||||
|
||||
cache = YAML.load(File.read(filename))
|
||||
if cache.version == current_version
|
||||
connection_pool.schema_cache = cache.dup
|
||||
else
|
||||
warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
|
||||
end
|
||||
if cache.version != current_version
|
||||
warn "Ignoring #{filename} because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
|
||||
next
|
||||
end
|
||||
|
||||
connection_pool.set_schema_cache(cache.dup)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -449,9 +449,7 @@ def load_seed
|
||||
# ==== Examples:
|
||||
# ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml")
|
||||
def dump_schema_cache(conn, filename)
|
||||
conn.schema_cache.clear!
|
||||
conn.data_sources.each { |table| conn.schema_cache.add(table) }
|
||||
open(filename, "wb") { |f| f.write(YAML.dump(conn.schema_cache)) }
|
||||
conn.schema_cache.dump_to(filename)
|
||||
end
|
||||
|
||||
def clear_schema_cache(filename)
|
||||
|
@ -16,59 +16,62 @@ def test_primary_key
|
||||
end
|
||||
|
||||
def test_yaml_dump_and_load
|
||||
@cache.columns("posts")
|
||||
@cache.columns_hash("posts")
|
||||
@cache.data_sources("posts")
|
||||
@cache.primary_keys("posts")
|
||||
@cache.indexes("posts")
|
||||
# Create an empty cache.
|
||||
cache = SchemaCache.new @connection
|
||||
|
||||
tempfile = Tempfile.new(["schema_cache-", ".yml"])
|
||||
# Dump it. It should get populated before dumping.
|
||||
cache.dump_to(tempfile.path)
|
||||
|
||||
# Load the cache.
|
||||
cache = SchemaCache.load_from(tempfile.path)
|
||||
|
||||
# Give it a connection. Usually the connection
|
||||
# would get set on the cache when it's retrieved
|
||||
# from the pool.
|
||||
cache.connection = @connection
|
||||
|
||||
new_cache = YAML.load(YAML.dump(@cache))
|
||||
assert_no_queries do
|
||||
assert_equal 12, new_cache.columns("posts").size
|
||||
assert_equal 12, new_cache.columns_hash("posts").size
|
||||
assert new_cache.data_sources("posts")
|
||||
assert_equal "id", new_cache.primary_keys("posts")
|
||||
assert_equal 1, new_cache.indexes("posts").size
|
||||
assert_equal @database_version.to_s, new_cache.database_version.to_s
|
||||
assert_equal 12, cache.columns("posts").size
|
||||
assert_equal 12, cache.columns_hash("posts").size
|
||||
assert cache.data_sources("posts")
|
||||
assert_equal "id", cache.primary_keys("posts")
|
||||
assert_equal 1, cache.indexes("posts").size
|
||||
assert_equal @database_version.to_s, cache.database_version.to_s
|
||||
end
|
||||
ensure
|
||||
tempfile.unlink
|
||||
end
|
||||
|
||||
def test_yaml_loads_5_1_dump
|
||||
@cache = YAML.load(File.read(schema_dump_path))
|
||||
cache = SchemaCache.load_from(schema_dump_path)
|
||||
cache.connection = @connection
|
||||
|
||||
assert_no_queries do
|
||||
assert_equal 11, @cache.columns("posts").size
|
||||
assert_equal 11, @cache.columns_hash("posts").size
|
||||
assert @cache.data_sources("posts")
|
||||
assert_equal "id", @cache.primary_keys("posts")
|
||||
assert_equal 11, cache.columns("posts").size
|
||||
assert_equal 11, cache.columns_hash("posts").size
|
||||
assert cache.data_sources("posts")
|
||||
assert_equal "id", cache.primary_keys("posts")
|
||||
end
|
||||
end
|
||||
|
||||
def test_yaml_loads_5_1_dump_without_indexes_still_queries_for_indexes
|
||||
@cache = YAML.load(File.read(schema_dump_path))
|
||||
|
||||
# Simulate assignment in railtie after loading the cache.
|
||||
old_cache, @connection.schema_cache = @connection.schema_cache, @cache
|
||||
cache = SchemaCache.load_from(schema_dump_path)
|
||||
cache.connection = @connection
|
||||
|
||||
assert_queries :any, ignore_none: true do
|
||||
assert_equal 1, @cache.indexes("posts").size
|
||||
assert_equal 1, cache.indexes("posts").size
|
||||
end
|
||||
ensure
|
||||
@connection.schema_cache = old_cache
|
||||
end
|
||||
|
||||
def test_yaml_loads_5_1_dump_without_database_version_still_queries_for_database_version
|
||||
@cache = YAML.load(File.read(schema_dump_path))
|
||||
|
||||
# Simulate assignment in railtie after loading the cache.
|
||||
old_cache, @connection.schema_cache = @connection.schema_cache, @cache
|
||||
cache = SchemaCache.load_from(schema_dump_path)
|
||||
cache.connection = @connection
|
||||
|
||||
# We can't verify queries get executed because the database version gets
|
||||
# cached in both MySQL and PostgreSQL outside of the schema cache.
|
||||
assert_nil @cache.instance_variable_get(:@database_version)
|
||||
assert_equal @database_version.to_s, @cache.database_version.to_s
|
||||
ensure
|
||||
@connection.schema_cache = old_cache
|
||||
assert_nil cache.instance_variable_get(:@database_version)
|
||||
assert_equal @database_version.to_s, cache.database_version.to_s
|
||||
end
|
||||
|
||||
def test_primary_key_for_non_existent_table
|
||||
@ -115,25 +118,50 @@ def test_clearing
|
||||
assert_nil @cache.instance_variable_get(:@database_version)
|
||||
end
|
||||
|
||||
def test_dump_and_load
|
||||
@cache.columns("posts")
|
||||
@cache.columns_hash("posts")
|
||||
@cache.data_sources("posts")
|
||||
@cache.primary_keys("posts")
|
||||
@cache.indexes("posts")
|
||||
def test_marshal_dump_and_load
|
||||
# Create an empty cache.
|
||||
cache = SchemaCache.new @connection
|
||||
|
||||
@cache = Marshal.load(Marshal.dump(@cache))
|
||||
# Populate it.
|
||||
cache.add("posts")
|
||||
|
||||
# Create a new cache by marchal dumping / loading.
|
||||
cache = Marshal.load(Marshal.dump(cache))
|
||||
|
||||
assert_no_queries do
|
||||
assert_equal 12, @cache.columns("posts").size
|
||||
assert_equal 12, @cache.columns_hash("posts").size
|
||||
assert @cache.data_sources("posts")
|
||||
assert_equal "id", @cache.primary_keys("posts")
|
||||
assert_equal 1, @cache.indexes("posts").size
|
||||
assert_equal @database_version.to_s, @cache.database_version.to_s
|
||||
assert_equal 12, cache.columns("posts").size
|
||||
assert_equal 12, cache.columns_hash("posts").size
|
||||
assert cache.data_sources("posts")
|
||||
assert_equal "id", cache.primary_keys("posts")
|
||||
assert_equal 1, cache.indexes("posts").size
|
||||
assert_equal @database_version.to_s, cache.database_version.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def test_marshal_dump_and_load_via_disk
|
||||
# Create an empty cache.
|
||||
cache = SchemaCache.new @connection
|
||||
|
||||
tempfile = Tempfile.new(["schema_cache-", ".dump"])
|
||||
# Dump it. It should get populated before dumping.
|
||||
cache.dump_to(tempfile.path)
|
||||
|
||||
# Load a new cache.
|
||||
cache = SchemaCache.load_from(tempfile.path)
|
||||
cache.connection = @connection
|
||||
|
||||
assert_no_queries do
|
||||
assert_equal 12, cache.columns("posts").size
|
||||
assert_equal 12, cache.columns_hash("posts").size
|
||||
assert cache.data_sources("posts")
|
||||
assert_equal "id", cache.primary_keys("posts")
|
||||
assert_equal 1, cache.indexes("posts").size
|
||||
assert_equal @database_version.to_s, cache.database_version.to_s
|
||||
end
|
||||
ensure
|
||||
tempfile.unlink
|
||||
end
|
||||
|
||||
def test_data_source_exist
|
||||
assert @cache.data_source_exists?("posts")
|
||||
assert_not @cache.data_source_exists?("foo")
|
||||
|
Loading…
Reference in New Issue
Block a user