Use YAML to serialize schema cache

This commit is contained in:
Kir Shatrov 2016-11-13 22:18:14 -05:00
parent 6d6249b1c1
commit 4c00c6ed23
7 changed files with 435 additions and 17 deletions

View File

@ -40,6 +40,28 @@ module ActiveRecord
Base.human_attribute_name(@name)
end
def init_with(coder)
@name = coder["name"]
@table_name = coder["table_name"]
@sql_type_metadata = coder["sql_type_metadata"]
@null = coder["null"]
@default = coder["default"]
@default_function = coder["default_function"]
@collation = coder["collation"]
@comment = coder["comment"]
end
def encode_with(coder)
coder["name"] = @name
coder["table_name"] = @table_name
coder["sql_type_metadata"] = @sql_type_metadata
coder["null"] = @null
coder["default"] = @default
coder["default_function"] = @default_function
coder["collation"] = @collation
coder["comment"] = @comment
end
def ==(other)
other.is_a?(Column) &&
attributes_for_hash == other.attributes_for_hash

View File

@ -21,6 +21,22 @@ module ActiveRecord
@data_sources = @data_sources.dup
end
def encode_with(coder)
coder["columns"] = @columns
coder["columns_hash"] = @columns_hash
coder["primary_keys"] = @primary_keys
coder["data_sources"] = @data_sources
coder["version"] = ActiveRecord::Migrator.current_version
end
def init_with(coder)
@columns = coder["columns"]
@columns_hash = coder["columns_hash"]
@primary_keys = coder["primary_keys"]
@data_sources = coder["data_sources"]
@version = coder["version"]
end
def primary_keys(table_name)
@primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
end

View File

@ -82,15 +82,15 @@ module ActiveRecord
if config.active_record.delete(:use_schema_cache_dump)
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
filename = File.join(app.config.paths["db"].first, "schema_cache.yml")
if File.file?(filename)
cache = Marshal.load File.binread filename
cache = YAML.load(File.read(filename))
if cache.version == ActiveRecord::Migrator.current_version
self.connection.schema_cache = cache
self.connection_pool.schema_cache = cache.dup
else
warn "Ignoring db/schema_cache.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
end
end
end

View File

@ -265,19 +265,19 @@ db_namespace = namespace :db do
end
namespace :cache do
desc "Creates a db/schema_cache.dump file."
desc "Creates a db/schema_cache.yml file."
task dump: [:environment, :load_config] do
con = ActiveRecord::Base.connection
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
conn = ActiveRecord::Base.connection
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
con.schema_cache.clear!
con.data_sources.each { |table| con.schema_cache.add(table) }
open(filename, "wb") { |f| f.write(Marshal.dump(con.schema_cache)) }
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)) }
end
desc "Clears a db/schema_cache.dump file."
desc "Clears a db/schema_cache.yml file."
task clear: [:environment, :load_config] do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
rm_f filename, verbose: false
end
end

View File

@ -0,0 +1,345 @@
--- !ruby/object:ActiveRecord::ConnectionAdapters::SchemaCache
columns:
posts:
- &1 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: id
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: INTEGER
type: :integer
limit:
precision:
scale:
'null': false
default:
default_function:
collation:
comment:
- &2 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: author_id
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: integer
type: :integer
limit:
precision:
scale:
'null': true
default:
default_function:
collation:
comment:
- &3 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: title
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: varchar
type: :string
limit:
precision:
scale:
'null': false
default:
default_function:
collation:
comment:
- &4 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: body
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: text
type: :text
limit:
precision:
scale:
'null': false
default:
default_function:
collation:
comment:
- &5 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: type
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: varchar
type: :string
limit:
precision:
scale:
'null': true
default:
default_function:
collation:
comment:
- &6 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: comments_count
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: integer
type: :integer
limit:
precision:
scale:
'null': true
default: '0'
default_function:
collation:
comment:
- &7 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: taggings_with_delete_all_count
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: integer
type: :integer
limit:
precision:
scale:
'null': true
default: '0'
default_function:
collation:
comment:
- &8 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: taggings_with_destroy_count
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: integer
type: :integer
limit:
precision:
scale:
'null': true
default: '0'
default_function:
collation:
comment:
- &9 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: tags_count
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: integer
type: :integer
limit:
precision:
scale:
'null': true
default: '0'
default_function:
collation:
comment:
- &10 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: tags_with_destroy_count
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: integer
type: :integer
limit:
precision:
scale:
'null': true
default: '0'
default_function:
collation:
comment:
- &11 !ruby/object:ActiveRecord::ConnectionAdapters::Column
name: tags_with_nullify_count
table_name: posts
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: integer
type: :integer
limit:
precision:
scale:
'null': true
default: '0'
default_function:
collation:
comment:
columns_hash:
posts:
id: *1
author_id: *2
title: *3
body: *4
type: *5
comments_count: *6
taggings_with_delete_all_count: *7
taggings_with_destroy_count: *8
tags_count: *9
tags_with_destroy_count: *10
tags_with_nullify_count: *11
primary_keys:
posts: id
data_sources:
ar_internal_metadata: true
table_with_autoincrement: true
accounts: true
admin_accounts: true
admin_users: true
aircraft: true
articles: true
articles_magazines: true
articles_tags: true
audit_logs: true
authors: true
author_addresses: true
author_favorites: true
auto_id_tests: true
binaries: true
birds: true
books: true
booleans: true
bulbs: true
CamelCase: true
cars: true
carriers: true
categories: true
categories_posts: true
categorizations: true
citations: true
clubs: true
collections: true
colnametests: true
columns: true
comments: true
companies: true
content: true
content_positions: true
vegetables: true
computers: true
computers_developers: true
contracts: true
customers: true
customer_carriers: true
dashboards: true
developers: true
developers_projects: true
dog_lovers: true
dogs: true
doubloons: true
edges: true
engines: true
entrants: true
essays: true
events: true
eyes: true
funny_jokes: true
cold_jokes: true
friendships: true
goofy_string_id: true
having: true
guids: true
guitars: true
inept_wizards: true
integer_limits: true
invoices: true
iris: true
items: true
jobs: true
jobs_pool: true
keyboards: true
legacy_things: true
lessons: true
lessons_students: true
students: true
lint_models: true
line_items: true
lions: true
lock_without_defaults: true
lock_without_defaults_cust: true
magazines: true
mateys: true
members: true
member_details: true
member_friends: true
memberships: true
member_types: true
mentors: true
minivans: true
minimalistics: true
mixed_case_monkeys: true
mixins: true
movies: true
notifications: true
numeric_data: true
orders: true
organizations: true
owners: true
paint_colors: true
paint_textures: true
parrots: true
parrots_pirates: true
parrots_treasures: true
people: true
peoples_treasures: true
personal_legacy_things: true
pets: true
pets_treasures: true
pirates: true
posts: true
serialized_posts: true
images: true
price_estimates: true
products: true
product_types: true
projects: true
randomly_named_table1: true
randomly_named_table2: true
randomly_named_table3: true
ratings: true
readers: true
references: true
shape_expressions: true
ships: true
ship_parts: true
prisoners: true
shop_accounts: true
speedometers: true
sponsors: true
string_key_objects: true
subscribers: true
subscriptions: true
tags: true
taggings: true
tasks: true
topics: true
toys: true
traffic_lights: true
treasures: true
tuning_pegs: true
tyres: true
variants: true
vertices: true
warehouse-things: true
circles: true
squares: true
triangles: true
non_poly_ones: true
non_poly_twos: true
men: true
faces: true
interests: true
zines: true
wheels: true
countries: true
treaties: true
countries_treaties: true
liquid: true
molecules: true
electrons: true
weirds: true
nodes: true
trees: true
hotels: true
departments: true
cake_designers: true
drink_designers: true
chefs: true
recipes: true
records: true
overloaded_types: true
users: true
test_with_keyword_column_name: true
fk_test_has_pk: true
fk_test_has_fk: true
version: 0

View File

@ -12,6 +12,33 @@ module ActiveRecord
assert_equal "id", @cache.primary_keys("posts")
end
def test_yaml_dump_and_load
@cache.columns("posts")
@cache.columns_hash("posts")
@cache.data_sources("posts")
@cache.primary_keys("posts")
new_cache = YAML.load(YAML.dump(@cache))
assert_no_queries do
assert_equal 11, new_cache.columns("posts").size
assert_equal 11, new_cache.columns_hash("posts").size
assert new_cache.data_sources("posts")
assert_equal "id", new_cache.primary_keys("posts")
end
end
def test_yaml_loads_5_1_dump
body = File.open(schema_dump_path).read
cache = YAML.load(body)
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")
end
end
def test_primary_key_for_non_existent_table
assert_nil @cache.primary_keys("omgponies")
end
@ -45,10 +72,12 @@ module ActiveRecord
@cache = Marshal.load(Marshal.dump(@cache))
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_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")
end
end
def test_table_methods_deprecation
@ -56,6 +85,12 @@ module ActiveRecord
assert_deprecated { assert @cache.tables("posts") }
assert_deprecated { @cache.clear_table_cache!("posts") }
end
private
def schema_dump_path
"test/assets/schema_dump_5_1.yml"
end
end
end
end

View File

@ -387,14 +387,14 @@ module ApplicationTests
bin/rails generate model product name:string;
bin/rails db:migrate db:schema:cache:dump`
end
assert File.exist?(File.join(app_path, "db", "schema_cache.dump"))
assert File.exist?(File.join(app_path, "db", "schema_cache.yml"))
end
def test_rake_clear_schema_cache
Dir.chdir(app_path) do
`bin/rails db:schema:cache:dump db:schema:cache:clear`
end
assert !File.exist?(File.join(app_path, "db", "schema_cache.dump"))
assert !File.exist?(File.join(app_path, "db", "schema_cache.yml"))
end
def test_copy_templates