1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

introduce conn.data_source_exists? and conn.data_sources.

These new methods are used from the Active Record model layer to
determine which relations are viable to back a model. These new methods
allow us to change `conn.tables` in the future to only return tables and
no views. Same for `conn.table_exists?`.

The goal is to provide the following introspection methods on the
connection:

* `tables`
* `table_exists?`
* `views`
* `view_exists?`
* `data_sources` (views + tables)
* `data_source_exists?` (views + tables)
This commit is contained in:
Yves Senn 2015-09-22 15:58:18 +02:00
parent 1165e9c898
commit 152b85f06c
16 changed files with 108 additions and 34 deletions

View file

@ -1,3 +1,13 @@
* Introduce `connection.data_sources` and `connection.data_source_exists?`.
These methods determine what relations can be used to back Active Record
models (usually tables and views).
Also deprecate `SchemaCache#tables`, `SchemaCache#table_exists?` and
`SchemaCache#clear_table_cache!` in favor of their new data source
counterparts.
*Yves Senn*, *Matthew Draper*
* `ActiveRecord::Tasks::MySQLDatabaseTasks` fails if shellout to
mysql commands (like `mysqldump`) is not successful.

View file

@ -23,6 +23,20 @@ module ActiveRecord
table_name[0...table_alias_length].tr('.', '_')
end
# Returns the relation names useable to back Active Record models.
# For most adapters this means all #tables and #views.
def data_sources
tables | views
end
# Checks to see if the data source +name+ exists on the database.
#
# data_source_exists?(:ebooks)
#
def data_source_exists?(name)
data_sources.include?(name.to_s)
end
# Returns an array of table names defined in the database.
def tables(name = nil)
raise NotImplementedError, "#tables is not implemented"

View file

@ -628,6 +628,7 @@ module ActiveRecord
def tables(name = nil) # :nodoc:
select_values("SHOW FULL TABLES", 'SCHEMA')
end
alias data_sources tables
def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
@ -644,6 +645,7 @@ module ActiveRecord
select_values(sql, 'SCHEMA').any?
end
alias data_source_exists? table_exists?
def views # :nodoc:
select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')

View file

@ -73,6 +73,16 @@ module ActiveRecord
select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end
def data_sources # :nodoc
select_values(<<-SQL, 'SCHEMA')
SELECT c.relname
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND n.nspname = ANY (current_schemas(false))
SQL
end
# Returns true if table exists.
# If the schema is not specified as part of +name+ then it will only find tables within
# the current schema search path (regardless of permissions to access tables in other schemas)
@ -89,6 +99,7 @@ module ActiveRecord
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL
end
alias data_source_exists? table_exists?
def views # :nodoc:
select_values(<<-SQL, 'SCHEMA')

View file

@ -10,7 +10,7 @@ module ActiveRecord
@columns = {}
@columns_hash = {}
@primary_keys = {}
@tables = {}
@data_sources = {}
end
def initialize_dup(other)
@ -18,33 +18,38 @@ module ActiveRecord
@columns = @columns.dup
@columns_hash = @columns_hash.dup
@primary_keys = @primary_keys.dup
@tables = @tables.dup
@data_sources = @data_sources.dup
end
def primary_keys(table_name)
@primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
@primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
end
# A cached lookup for table existence.
def table_exists?(name)
prepare_tables if @tables.empty?
return @tables[name] if @tables.key? name
def data_source_exists?(name)
prepare_data_sources if @data_sources.empty?
return @data_sources[name] if @data_sources.key? name
@tables[name] = connection.table_exists?(name)
@data_sources[name] = connection.data_source_exists?(name)
end
alias table_exists? data_source_exists?
deprecate :table_exists? => "use #data_source_exists? instead"
# Add internal cache for table with +table_name+.
def add(table_name)
if table_exists?(table_name)
if data_source_exists?(table_name)
primary_keys(table_name)
columns(table_name)
columns_hash(table_name)
end
end
def tables(name)
@tables[name]
def data_sources(name)
@data_sources[name]
end
alias tables data_sources
deprecate :tables => "use #data_sources instead"
# Get the columns for a table
def columns(table_name)
@ -64,36 +69,38 @@ module ActiveRecord
@columns.clear
@columns_hash.clear
@primary_keys.clear
@tables.clear
@data_sources.clear
@version = nil
end
def size
[@columns, @columns_hash, @primary_keys, @tables].map(&:size).inject :+
[@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+
end
# Clear out internal caches for table with +table_name+.
def clear_table_cache!(table_name)
@columns.delete table_name
@columns_hash.delete table_name
@primary_keys.delete table_name
@tables.delete table_name
# Clear out internal caches for the data source +name+.
def clear_data_source_cache!(name)
@columns.delete name
@columns_hash.delete name
@primary_keys.delete name
@data_sources.delete name
end
alias clear_table_cache! clear_data_source_cache!
deprecate :clear_table_cache! => "use #clear_data_source_cache! instead"
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
@version = ActiveRecord::Migrator.current_version
[@version, @columns, @columns_hash, @primary_keys, @tables]
[@version, @columns, @columns_hash, @primary_keys, @data_sources]
end
def marshal_load(array)
@version, @columns, @columns_hash, @primary_keys, @tables = array
@version, @columns, @columns_hash, @primary_keys, @data_sources = array
end
private
def prepare_tables
connection.tables.each { |table| @tables[table] = true }
def prepare_data_sources
connection.data_sources.each { |source| @data_sources[source] = true }
end
end
end

View file

@ -319,10 +319,12 @@ module ActiveRecord
row['name']
end
end
alias data_sources tables
def table_exists?(table_name)
table_name && tables(nil, table_name).any?
end
alias data_source_exists? table_exists?
def views # :nodoc:
select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')

View file

@ -213,7 +213,7 @@ module ActiveRecord
# Indicates whether the table associated with this class exists
def table_exists?
connection.schema_cache.table_exists?(table_name)
connection.schema_cache.data_source_exists?(table_name)
end
def attributes_builder # :nodoc:
@ -290,7 +290,7 @@ module ActiveRecord
def reset_column_information
connection.clear_cache!
undefine_attribute_methods
connection.schema_cache.clear_table_cache!(table_name)
connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
end

View file

@ -255,7 +255,7 @@ db_namespace = namespace :db do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
con.schema_cache.clear!
con.tables.each { |table| con.schema_cache.add(table) }
con.data_sources.each { |table| con.schema_cache.add(table) }
open(filename, 'wb') { |f| f.write(Marshal.dump(con.schema_cache)) }
end

View file

@ -89,7 +89,7 @@ HEADER
end
def tables(stream)
sorted_tables = @connection.tables.sort - @connection.views
sorted_tables = @connection.data_sources.sort - @connection.views
sorted_tables.each do |table_name|
table(table_name, stream) unless ignored?(table_name)

View file

@ -20,7 +20,7 @@ module ActiveRecord
private
def column_for(attribute_name)
if connection.schema_cache.table_exists?(table_name)
if connection.schema_cache.data_source_exists?(table_name)
connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
end
end

View file

@ -7,7 +7,7 @@ module ActiveRecord
module ConnectionAdapters
class FakeAdapter < AbstractAdapter
attr_accessor :tables, :primary_keys
attr_accessor :data_sources, :primary_keys
@columns = Hash.new { |h,k| h[k] = [] }
class << self
@ -16,7 +16,7 @@ module ActiveRecord
def initialize(connection, logger)
super
@tables = []
@data_sources = []
@primary_keys = {}
@columns = self.class.columns
end
@ -37,7 +37,7 @@ module ActiveRecord
@columns[table_name]
end
def table_exists?(*)
def data_source_exists?(*)
true
end

View file

@ -36,6 +36,21 @@ module ActiveRecord
assert !@connection.table_exists?(nil)
end
def test_data_sources
data_sources = @connection.data_sources
assert data_sources.include?("accounts")
assert data_sources.include?("authors")
assert data_sources.include?("tasks")
assert data_sources.include?("topics")
end
def test_data_source_exists?
assert @connection.data_source_exists?("accounts")
assert @connection.data_source_exists?(:accounts)
assert_not @connection.data_source_exists?("nonexistingtable")
assert_not @connection.data_source_exists?(nil)
end
def test_indexes
idx_name = "accounts_idx"

View file

@ -5,9 +5,11 @@ require 'support/schema_dumping_helper'
module PGSchemaHelper
def with_schema_search_path(schema_search_path)
@connection.schema_search_path = schema_search_path
@connection.schema_cache.clear!
yield if block_given?
ensure
@connection.schema_search_path = "'$user', public"
@connection.schema_cache.clear!
end
end

View file

@ -29,7 +29,7 @@ module ActiveRecord
def test_clearing
@cache.columns('posts')
@cache.columns_hash('posts')
@cache.tables('posts')
@cache.data_sources('posts')
@cache.primary_keys('posts')
@cache.clear!
@ -40,17 +40,22 @@ module ActiveRecord
def test_dump_and_load
@cache.columns('posts')
@cache.columns_hash('posts')
@cache.tables('posts')
@cache.data_sources('posts')
@cache.primary_keys('posts')
@cache = Marshal.load(Marshal.dump(@cache))
assert_equal 11, @cache.columns('posts').size
assert_equal 11, @cache.columns_hash('posts').size
assert @cache.tables('posts')
assert @cache.data_sources('posts')
assert_equal 'id', @cache.primary_keys('posts')
end
def test_table_methods_deprecation
assert_deprecated { assert @cache.table_exists?('posts') }
assert_deprecated { assert @cache.tables('posts') }
assert_deprecated { @cache.clear_table_cache!('posts') }
end
end
end
end

View file

@ -44,9 +44,15 @@ module ViewBehavior
def test_table_exists
view_name = Ebook.table_name
# TODO: switch this assertion around once we changed #tables to not return views.
assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
end
def test_views_ara_valid_data_sources
view_name = Ebook.table_name
assert @connection.data_source_exists?(view_name), "'#{view_name}' should be a data source"
end
def test_column_definitions
assert_equal([["id", :integer],
["name", :string],

View file

@ -3,7 +3,7 @@ module ContactFakeColumns
base.class_eval do
establish_connection(:adapter => 'fake')
connection.tables = [table_name]
connection.data_sources = [table_name]
connection.primary_keys = {
table_name => 'id'
}