Merge pull request #35891 from Shopify/schema-cache-deduplication
Deduplicate various Active Record schema cache structures
This commit is contained in:
commit
aae270de9e
|
@ -5,6 +5,8 @@ module ActiveRecord
|
|||
module ConnectionAdapters
|
||||
# An abstract definition of a column in a table.
|
||||
class Column
|
||||
include Deduplicable
|
||||
|
||||
attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment
|
||||
|
||||
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
|
||||
|
@ -76,6 +78,7 @@ module ActiveRecord
|
|||
def hash
|
||||
Column.hash ^
|
||||
name.hash ^
|
||||
name.encoding.hash ^
|
||||
default.hash ^
|
||||
sql_type_metadata.hash ^
|
||||
null.hash ^
|
||||
|
@ -83,6 +86,17 @@ module ActiveRecord
|
|||
collation.hash ^
|
||||
comment.hash
|
||||
end
|
||||
|
||||
private
|
||||
def deduplicated
|
||||
@name = -name
|
||||
@sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
|
||||
@default = -default if default
|
||||
@default_function = -default_function if default_function
|
||||
@collation = -collation if collation
|
||||
@comment = -comment if comment
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
class NullColumn < Column
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
module Deduplicable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
def registry
|
||||
@registry ||= {}
|
||||
end
|
||||
|
||||
def new(*)
|
||||
super.deduplicate
|
||||
end
|
||||
end
|
||||
|
||||
def deduplicate
|
||||
self.class.registry[self] ||= deduplicated
|
||||
end
|
||||
alias :-@ :deduplicate
|
||||
|
||||
private
|
||||
def deduplicated
|
||||
freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,9 +6,11 @@ module ActiveRecord
|
|||
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
|
||||
undef to_yaml if method_defined?(:to_yaml)
|
||||
|
||||
include Deduplicable
|
||||
|
||||
attr_reader :extra
|
||||
|
||||
def initialize(type_metadata, extra: "")
|
||||
def initialize(type_metadata, extra: nil)
|
||||
super(type_metadata)
|
||||
@extra = extra
|
||||
end
|
||||
|
@ -25,6 +27,13 @@ module ActiveRecord
|
|||
__getobj__.hash ^
|
||||
extra.hash
|
||||
end
|
||||
|
||||
private
|
||||
def deduplicated
|
||||
__setobj__(__getobj__.deduplicate)
|
||||
@extra = -extra if extra
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,6 +23,19 @@ module ActiveRecord
|
|||
def sql_type
|
||||
super.sub(/\[\]\z/, "")
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.is_a?(Column) &&
|
||||
super &&
|
||||
serial? == other.serial?
|
||||
end
|
||||
alias :eql? :==
|
||||
|
||||
def hash
|
||||
Column.hash ^
|
||||
super.hash ^
|
||||
serial?.hash
|
||||
end
|
||||
end
|
||||
end
|
||||
PostgreSQLColumn = PostgreSQL::Column # :nodoc:
|
||||
|
|
|
@ -7,6 +7,8 @@ module ActiveRecord
|
|||
class TypeMetadata < DelegateClass(SqlTypeMetadata)
|
||||
undef to_yaml if method_defined?(:to_yaml)
|
||||
|
||||
include Deduplicable
|
||||
|
||||
attr_reader :oid, :fmod
|
||||
|
||||
def initialize(type_metadata, oid: nil, fmod: nil)
|
||||
|
@ -29,6 +31,12 @@ module ActiveRecord
|
|||
oid.hash ^
|
||||
fmod.hash
|
||||
end
|
||||
|
||||
private
|
||||
def deduplicated
|
||||
__setobj__(__getobj__.deduplicate)
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata
|
||||
|
|
|
@ -129,10 +129,29 @@ module ActiveRecord
|
|||
|
||||
def marshal_load(array)
|
||||
@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
|
||||
@indexes = @indexes || {}
|
||||
@indexes ||= {}
|
||||
|
||||
@columns = deep_deduplicate(@columns)
|
||||
@columns_hash = deep_deduplicate(@columns_hash)
|
||||
@primary_keys = deep_deduplicate(@primary_keys)
|
||||
@data_sources = deep_deduplicate(@data_sources)
|
||||
@indexes = deep_deduplicate(@indexes)
|
||||
end
|
||||
|
||||
private
|
||||
def deep_deduplicate(value)
|
||||
case value
|
||||
when Hash
|
||||
value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
|
||||
when Array
|
||||
value.map { |i| deep_deduplicate(i) }
|
||||
when String, Deduplicable
|
||||
-value
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_data_sources
|
||||
connection.data_sources.each { |source| @data_sources[source] = true }
|
||||
end
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_record/connection_adapters/deduplicable"
|
||||
|
||||
module ActiveRecord
|
||||
# :stopdoc:
|
||||
module ConnectionAdapters
|
||||
class SqlTypeMetadata
|
||||
include Deduplicable
|
||||
|
||||
attr_reader :sql_type, :type, :limit, :precision, :scale
|
||||
|
||||
def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
|
||||
|
@ -32,6 +36,12 @@ module ActiveRecord
|
|||
precision.hash >> 1 ^
|
||||
scale.hash >> 2
|
||||
end
|
||||
|
||||
private
|
||||
def deduplicated
|
||||
@sql_type = -sql_type
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -383,6 +383,7 @@ module ActiveRecord
|
|||
if from_primary_key.is_a?(Array)
|
||||
@definition.primary_keys from_primary_key
|
||||
end
|
||||
|
||||
columns(from).each do |column|
|
||||
column_name = options[:rename] ?
|
||||
(options[:rename][column.name] ||
|
||||
|
|
|
@ -32,7 +32,8 @@ module ActiveRecord
|
|||
name.to_s,
|
||||
options[:default],
|
||||
fetch_type_metadata(sql_type),
|
||||
options[:null])
|
||||
options[:null],
|
||||
)
|
||||
end
|
||||
|
||||
def columns(table_name)
|
||||
|
|
|
@ -1141,11 +1141,14 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
def test_clear_cache!
|
||||
# preheat cache
|
||||
c1 = Post.connection.schema_cache.columns("posts")
|
||||
assert_not_equal 0, Post.connection.schema_cache.size
|
||||
|
||||
ActiveRecord::Base.clear_cache!
|
||||
assert_equal 0, Post.connection.schema_cache.size
|
||||
|
||||
c2 = Post.connection.schema_cache.columns("posts")
|
||||
c1.each_with_index do |v, i|
|
||||
assert_not_same v, c2[i]
|
||||
end
|
||||
assert_not_equal 0, Post.connection.schema_cache.size
|
||||
|
||||
assert_equal c1, c2
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
|
|||
include JsonSerializationHelpers
|
||||
|
||||
class NamespacedContact < Contact
|
||||
column :name, :string
|
||||
column :name, "string"
|
||||
end
|
||||
|
||||
def setup
|
||||
|
|
|
@ -10,14 +10,14 @@ module ContactFakeColumns
|
|||
table_name => "id"
|
||||
}
|
||||
|
||||
column :id, :integer
|
||||
column :name, :string
|
||||
column :age, :integer
|
||||
column :avatar, :binary
|
||||
column :created_at, :datetime
|
||||
column :awesome, :boolean
|
||||
column :preferences, :string
|
||||
column :alternative_id, :integer
|
||||
column :id, "integer"
|
||||
column :name, "string"
|
||||
column :age, "integer"
|
||||
column :avatar, "binary"
|
||||
column :created_at, "datetime"
|
||||
column :awesome, "boolean"
|
||||
column :preferences, "string"
|
||||
column :alternative_id, "integer"
|
||||
|
||||
serialize :preferences
|
||||
|
||||
|
@ -37,7 +37,7 @@ end
|
|||
|
||||
class ContactSti < ActiveRecord::Base
|
||||
extend ContactFakeColumns
|
||||
column :type, :string
|
||||
column :type, "string"
|
||||
|
||||
def type; "ContactSti" end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue