Skip to content

Commit ef21ab6

Browse files
authored
Merge pull request rails#49415 from Shopify/schema-cache-server-version
Move the database version cache from schema cache to pool config
2 parents 9cb6f44 + a1c0173 commit ef21ab6

File tree

9 files changed

+39
-79
lines changed

9 files changed

+39
-79
lines changed

activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ def method_missing(*)
2222
end
2323
NULL_CONFIG = NullConfig.new # :nodoc:
2424

25+
def initialize
26+
super()
27+
@mutex = Mutex.new
28+
@server_version = nil
29+
end
30+
31+
def server_version(connection) # :nodoc:
32+
@server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
33+
end
34+
2535
def schema_reflection
2636
SchemaReflection.new(nil)
2737
end
@@ -111,7 +121,7 @@ class ConnectionPool
111121
attr_accessor :automatic_reconnect, :checkout_timeout
112122
attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
113123

114-
delegate :schema_reflection, :schema_reflection=, to: :pool_config
124+
delegate :schema_reflection, :schema_reflection=, :server_version, to: :pool_config
115125

116126
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
117127
# object which describes database connection information (e.g. adapter,

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ def get_database_version # :nodoc:
868868
end
869869

870870
def database_version # :nodoc:
871-
schema_cache.database_version
871+
pool.server_version(self)
872872
end
873873

874874
def check_version # :nodoc:

activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def configure_connection
174174
end
175175

176176
def full_version
177-
schema_cache.database_version.full_version_string
177+
database_version.full_version_string
178178
end
179179

180180
def get_full_version

activerecord/lib/active_record/connection_adapters/pool_config.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
module ActiveRecord
44
module ConnectionAdapters
55
class PoolConfig # :nodoc:
6-
include Mutex_m
6+
include MonitorMixin
77

88
attr_reader :db_config, :role, :shard
9-
attr_writer :schema_reflection
9+
attr_writer :schema_reflection, :server_version
1010
attr_accessor :connection_class
1111

1212
def schema_reflection
@@ -28,6 +28,7 @@ def disconnect_all!
2828

2929
def initialize(connection_class, db_config, role, shard)
3030
super()
31+
@server_version = nil
3132
@connection_class = connection_class
3233
@db_config = db_config
3334
@role = role
@@ -36,6 +37,10 @@ def initialize(connection_class, db_config, role, shard)
3637
INSTANCES[self] = self
3738
end
3839

40+
def server_version(connection)
41+
@server_version || synchronize { @server_version ||= connection.get_database_version }
42+
end
43+
3944
def connection_name
4045
if connection_class.primary_class?
4146
"ActiveRecord::Base"

activerecord/lib/active_record/connection_adapters/schema_cache.rb

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ def indexes(connection, table_name)
6666
cache(connection).indexes(connection, table_name)
6767
end
6868

69-
def database_version(connection)
70-
cache(connection).database_version(connection)
71-
end
72-
7369
def version(connection)
7470
cache(connection).version(connection)
7571
end
@@ -196,10 +192,6 @@ def indexes(table_name)
196192
@schema_reflection.indexes(@connection, table_name)
197193
end
198194

199-
def database_version
200-
@schema_reflection.database_version(@connection)
201-
end
202-
203195
def version
204196
@schema_reflection.version(@connection)
205197
end
@@ -264,7 +256,6 @@ def initialize
264256
@primary_keys = {}
265257
@data_sources = {}
266258
@indexes = {}
267-
@database_version = nil
268259
@version = nil
269260
end
270261

@@ -283,7 +274,6 @@ def encode_with(coder) # :nodoc:
283274
coder["data_sources"] = @data_sources.sort.to_h
284275
coder["indexes"] = @indexes.sort.to_h
285276
coder["version"] = @version
286-
coder["database_version"] = @database_version
287277
end
288278

289279
def init_with(coder)
@@ -293,7 +283,6 @@ def init_with(coder)
293283
@data_sources = coder["data_sources"]
294284
@indexes = coder["indexes"] || {}
295285
@version = coder["version"]
296-
@database_version = coder["database_version"]
297286

298287
unless coder["deduplicated"]
299288
derive_columns_hash_and_deduplicate_values
@@ -370,10 +359,6 @@ def indexes(connection, table_name)
370359
end
371360
end
372361

373-
def database_version(connection) # :nodoc:
374-
@database_version ||= connection.get_database_version
375-
end
376-
377362
def version(connection)
378363
@version ||= connection.schema_version
379364
end
@@ -401,7 +386,6 @@ def add_all(connection) # :nodoc:
401386
end
402387

403388
version(connection)
404-
database_version(connection)
405389
end
406390

407391
def dump_to(filename)
@@ -415,11 +399,11 @@ def dump_to(filename)
415399
end
416400

417401
def marshal_dump # :nodoc:
418-
[@version, @columns, {}, @primary_keys, @data_sources, @indexes, @database_version]
402+
[@version, @columns, {}, @primary_keys, @data_sources, @indexes]
419403
end
420404

421405
def marshal_load(array) # :nodoc:
422-
@version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
406+
@version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, _database_version = array
423407
@indexes ||= {}
424408

425409
derive_columns_hash_and_deduplicate_values

activerecord/lib/active_record/connection_adapters/trilogy_adapter.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ def reconnect
202202
end
203203

204204
def full_version
205-
schema_cache.database_version.full_version_string
205+
database_version.full_version_string
206206
end
207207

208208
def get_full_version

activerecord/lib/active_record/railtie.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,13 @@ class Railtie < Rails::Railtie # :nodoc:
162162
warn "Failed to validate the schema cache because of #{error.class}: #{error.message}"
163163
nil
164164
end
165-
next if current_version.nil?
166165

167-
if cache.schema_version != current_version
166+
if current_version.nil?
167+
connection_pool.schema_reflection.clear!
168+
next
169+
elsif cache.schema_version != current_version
168170
warn "Ignoring #{filename} because it has expired. The current schema version is #{current_version}, but the one in the schema cache file is #{cache.schema_version}."
171+
connection_pool.schema_reflection.clear!
169172
next
170173
end
171174
end

activerecord/test/cases/adapters/abstract_mysql_adapter/datetime_precision_quoting_test.rb

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,10 @@ def assert_match_quoted_microsecond_datetime(match)
3838
assert_match match, @connection.quoted_date(Time.now.change(sec: 55, usec: 123456))
3939
end
4040

41-
def stub_version(full_version_string)
42-
@connection.stub(:get_full_version, full_version_string) do
43-
@connection.schema_cache.clear!
44-
yield
45-
end
41+
def stub_version(full_version_string, &block)
42+
@connection.pool.pool_config.server_version = nil
43+
@connection.stub(:get_full_version, full_version_string, &block)
4644
ensure
47-
@connection.schema_cache.clear!
45+
@connection.pool.pool_config.server_version = nil
4846
end
4947
end

activerecord/test/cases/connection_adapters/schema_cache_test.rb

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ module ActiveRecord
66
module ConnectionAdapters
77
class SchemaCacheTest < ActiveRecord::TestCase
88
def setup
9-
@connection = ARUnit2Model.connection
10-
@cache = new_bound_reflection
11-
@database_version = @connection.get_database_version
9+
@connection = ARUnit2Model.connection
10+
@cache = new_bound_reflection
1211
@check_schema_cache_dump_version_was = SchemaReflection.check_schema_cache_dump_version
1312
end
1413

@@ -67,7 +66,6 @@ def test_yaml_dump_and_load
6766
assert cache.data_source_exists?("courses")
6867
assert_equal "id", cache.primary_keys("courses")
6968
assert_equal 1, cache.indexes("courses").size
70-
assert_equal @database_version.to_s, cache.database_version.to_s
7169
end
7270
ensure
7371
tempfile.unlink
@@ -104,7 +102,6 @@ def test_yaml_dump_and_load_with_gzip
104102
assert cache.data_source_exists?(@connection, "courses")
105103
assert_equal "id", cache.primary_keys(@connection, "courses")
106104
assert_equal 1, cache.indexes(@connection, "courses").size
107-
assert_equal @database_version.to_s, cache.database_version(@connection).to_s
108105
end
109106

110107
# Load the cache the usual way.
@@ -116,7 +113,6 @@ def test_yaml_dump_and_load_with_gzip
116113
assert cache.data_source_exists?("courses")
117114
assert_equal "id", cache.primary_keys("courses")
118115
assert_equal 1, cache.indexes("courses").size
119-
assert_equal @database_version.to_s, cache.database_version.to_s
120116
end
121117
ensure
122118
tempfile.unlink
@@ -141,18 +137,6 @@ def test_yaml_loads_5_1_dump_without_indexes_still_queries_for_indexes
141137
end
142138
end
143139

144-
def test_yaml_loads_5_1_dump_without_database_version_still_queries_for_database_version
145-
cache = load_bound_reflection(schema_dump_path)
146-
147-
# We can't verify queries get executed because the database version gets
148-
# cached in both MySQL and PostgreSQL outside of the schema cache.
149-
150-
assert_not_nil reflection = @cache.instance_variable_get(:@schema_reflection)
151-
assert_nil reflection.instance_variable_get(:@cache)
152-
153-
assert_equal @database_version.to_s, cache.database_version.to_s
154-
end
155-
156140
def test_primary_key_for_existent_table
157141
assert_equal "id", @cache.primary_keys("courses")
158142
end
@@ -189,18 +173,6 @@ def test_indexes_for_non_existent_table
189173
assert_equal [], @cache.indexes("omgponies")
190174
end
191175

192-
def test_caches_database_version
193-
@cache.database_version # cache database_version
194-
195-
assert_no_queries do
196-
assert_equal @database_version.to_s, @cache.database_version.to_s
197-
198-
if current_adapter?(:Mysql2Adapter, :TrilogyAdapter)
199-
assert_not_nil @cache.database_version.full_version_string
200-
end
201-
end
202-
end
203-
204176
def test_clearing
205177
@cache.columns("courses")
206178
@cache.columns_hash("courses")
@@ -211,9 +183,6 @@ def test_clearing
211183
@cache.clear!
212184

213185
assert_equal 0, @cache.size
214-
reflection = @cache.instance_variable_get(:@schema_reflection)
215-
schema_cache = reflection.instance_variable_get(:@cache)
216-
assert_nil schema_cache.instance_variable_get(:@database_version)
217186
end
218187

219188
def test_marshal_dump_and_load
@@ -223,10 +192,6 @@ def test_marshal_dump_and_load
223192
# Populate it.
224193
cache.add("courses")
225194

226-
# We're going to manually dump, so we also need to force
227-
# database_version to be stored.
228-
cache.database_version
229-
230195
# Create a new cache by marshal dumping / loading.
231196
cache = Marshal.load(Marshal.dump(cache.instance_variable_get(:@schema_reflection).instance_variable_get(:@cache)))
232197

@@ -236,7 +201,6 @@ def test_marshal_dump_and_load
236201
assert cache.data_source_exists?(@connection, "courses")
237202
assert_equal "id", cache.primary_keys(@connection, "courses")
238203
assert_equal 1, cache.indexes(@connection, "courses").size
239-
assert_equal @database_version.to_s, cache.database_version(@connection).to_s
240204
end
241205
end
242206

@@ -257,7 +221,6 @@ def test_marshal_dump_and_load_via_disk
257221
assert cache.data_source_exists?("courses")
258222
assert_equal "id", cache.primary_keys("courses")
259223
assert_equal 1, cache.indexes("courses").size
260-
assert_equal @database_version.to_s, cache.database_version.to_s
261224
end
262225
ensure
263226
tempfile.unlink
@@ -316,7 +279,6 @@ def test_marshal_dump_and_load_with_gzip
316279
assert cache.data_source_exists?(@connection, "courses")
317280
assert_equal "id", cache.primary_keys(@connection, "courses")
318281
assert_equal 1, cache.indexes(@connection, "courses").size
319-
assert_equal @database_version.to_s, cache.database_version(@connection).to_s
320282
end
321283

322284
# Load a new cache.
@@ -328,7 +290,6 @@ def test_marshal_dump_and_load_with_gzip
328290
assert cache.data_source_exists?("courses")
329291
assert_equal "id", cache.primary_keys("courses")
330292
assert_equal 1, cache.indexes("courses").size
331-
assert_equal @database_version.to_s, cache.database_version.to_s
332293
end
333294
ensure
334295
tempfile.unlink
@@ -389,28 +350,28 @@ def test_when_lazily_load_schema_cache_is_set_cache_is_lazily_populated_when_est
389350
ActiveRecord::Base.establish_connection(new_config)
390351

391352
# cache starts empty
392-
assert_equal 0, ActiveRecord::Base.connection.pool.schema_reflection.instance_variable_get(:@cache).size
353+
assert_nil ActiveRecord::Base.connection.pool.schema_reflection.instance_variable_get(:@cache)
393354

394355
# now we access the cache, causing it to load
395-
assert ActiveRecord::Base.connection.schema_cache.version
356+
assert_not_nil ActiveRecord::Base.connection.schema_cache.version
396357

397358
assert File.exist?(tempfile)
398-
assert ActiveRecord::Base.connection.pool.schema_reflection.instance_variable_get(:@cache)
359+
assert_not_nil ActiveRecord::Base.connection.pool.schema_reflection.instance_variable_get(:@cache)
399360

400361
# assert cache is still empty on new connection (precondition for the
401362
# following to show it is loading because of the config change)
402363
ActiveRecord::Base.establish_connection(new_config)
403364

404365
assert File.exist?(tempfile)
405-
assert_equal 0, ActiveRecord::Base.connection.pool.schema_reflection.instance_variable_get(:@cache).size
366+
assert_nil ActiveRecord::Base.connection.pool.schema_reflection.instance_variable_get(:@cache)
406367

407368
# cache is loaded upon connection when lazily loading is on
408369
old_config = ActiveRecord.lazily_load_schema_cache
409370
ActiveRecord.lazily_load_schema_cache = true
410371
ActiveRecord::Base.establish_connection(new_config)
411372

412373
assert File.exist?(tempfile)
413-
assert ActiveRecord::Base.connection.pool.schema_reflection.instance_variable_get(:@cache)
374+
assert_not_nil ActiveRecord::Base.connection.pool.schema_reflection.instance_variable_get(:@cache)
414375
ensure
415376
ActiveRecord.lazily_load_schema_cache = old_config
416377
ActiveRecord::Base.establish_connection(:arunit)
@@ -449,7 +410,6 @@ def test_when_lazily_load_schema_cache_is_set_cache_is_lazily_populated_when_est
449410
assert_equal expected, coder["data_sources"]
450411
assert_equal expected, coder["indexes"]
451412
assert coder.key?("version")
452-
assert coder.key?("database_version")
453413
end
454414

455415
private

0 commit comments

Comments
 (0)