Skip to content

Commit

Permalink
Add ability to ignore tables in the schema cache
Browse files Browse the repository at this point in the history
In cases where an application uses pt-osc or lhm they may have
temporary tables being used for migrations. Those tables shouldn't be
included by the schema cache because it makes the cache bigger and those
tables shouldn't ever be queried by the application. This feature allows
applications to configure a list of or regex of tables to ignore. The
behavior for ignored tables for each method in the schema cache
(`columns`, `columns_hash`, `primary_keys`, and `indexes`) matches the
behavior of a non-existent table. `columns` and `columns_hash` will
raise a database not found error, `primary_keys` will return `nil`. and
`indexes` will return an empty array. See #43105 which make behavior
across adapters consistent.

To use in an application configure `config.active_record.schema_cache_ignored_tables`
to an array of tables or regex's. Those tables will not be included in
the schema cache dump.
  • Loading branch information
eileencodes committed Aug 26, 2021
1 parent 1b2fa18 commit e3b4a46
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 2 deletions.
18 changes: 18 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,21 @@
* Add config option for ignoring tables when dumping the schema cache.

Applications can now be configured to ignore certain tables when dumping the schema cache.

The configuration option can table an array of tables:

```ruby
config.active_record.schema_cache_ignored_tables = ["ignored_table", "another_ignored_table"]
```

Or a regex:

```ruby
config.active_record.schema_cache_ignored_tables = [/^_/]
```

*Eileen M. Uchitelle*

* Make schema cache methods return consistent results.

Previously the schema cache methods `primary_keys`, `columns, `columns_hash`, and `indexes`
Expand Down
6 changes: 6 additions & 0 deletions activerecord/lib/active_record.rb
Expand Up @@ -170,6 +170,12 @@ module Tasks
autoload :TestDatabases, "active_record/test_databases"
autoload :TestFixtures, "active_record/fixtures"

# A list of tables or regex's to match tables to ignore when
# dumping the schema cache. For example if this is set to +[/^_/]+
# the schema cache will not dump tables named with an underscore.
singleton_class.attr_accessor :schema_cache_ignored_tables
self.schema_cache_ignored_tables = []

singleton_class.attr_accessor :legacy_connection_handling
self.legacy_connection_handling = true

Expand Down
23 changes: 21 additions & 2 deletions activerecord/lib/active_record/connection_adapters/schema_cache.rb
Expand Up @@ -86,6 +86,7 @@ def primary_keys(table_name)

# A cached lookup for table existence.
def data_source_exists?(name)
return if ignored_table?(name)
prepare_data_sources if @data_sources.empty?
return @data_sources[name] if @data_sources.key? name

Expand All @@ -108,6 +109,10 @@ def data_sources(name)

# Get the columns for a table
def columns(table_name)
if ignored_table?(table_name)
raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist"
end

@columns.fetch(table_name) do
@columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
end
Expand Down Expand Up @@ -166,7 +171,7 @@ def clear_data_source_cache!(name)

def dump_to(filename)
clear!
connection.data_sources.each { |table| add(table) }
tables_to_cache.each { |table| add(table) }
open(filename) { |f|
if filename.include?(".dump")
f.write(Marshal.dump(self))
Expand All @@ -190,6 +195,18 @@ def marshal_load(array)
end

private
def tables_to_cache
connection.data_sources.reject do |table|
ignored_table?(table)
end
end

def ignored_table?(table_name)
ActiveRecord.schema_cache_ignored_tables.any? do |ignored|
ignored === table_name
end
end

def reset_version!
@version = connection.migration_context.current_version
end
Expand All @@ -216,7 +233,9 @@ def deep_deduplicate(value)
end

def prepare_data_sources
connection.data_sources.each { |source| @data_sources[source] = true }
tables_to_cache.each do |source|
@data_sources[source] = true
end
end

def open(filename)
Expand Down
37 changes: 37 additions & 0 deletions activerecord/test/cases/connection_adapters/schema_cache_test.rb
Expand Up @@ -220,6 +220,43 @@ def test_marshal_dump_and_load_via_disk
tempfile.unlink
end

def test_marshal_dump_and_load_with_ignored_tables
old_ignore = ActiveRecord.schema_cache_ignored_tables
ActiveRecord.schema_cache_ignored_tables = ["p_schema_migrations"]
# 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 a table in the cache
assert cache.data_sources("posts"), "expected posts to be in the cached data_sources"
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 ignored table. Behavior should match non-existent table.
assert_nil cache.data_sources("p_schema_migrations"), "expected comments to not be in the cached data_sources"
assert_raises ActiveRecord::StatementInvalid do
cache.columns("p_schema_migrations")
end
assert_raises ActiveRecord::StatementInvalid do
cache.columns_hash("p_schema_migrations").size
end
assert_nil cache.primary_keys("p_schema_migrations")
assert_equal [], cache.indexes("p_schema_migrations")
ensure
tempfile.unlink
ActiveRecord.schema_cache_ignored_tables = old_ignore
end

def test_marshal_dump_and_load_with_gzip
# Create an empty cache.
cache = SchemaCache.new @connection
Expand Down

0 comments on commit e3b4a46

Please sign in to comment.