提交 7805fa2b 编写于 作者: Y Yves Senn

Merge pull request #21715 from rails/introduce_data_sources

introduce `conn.data_source_exists?` and `conn.data_sources`.
* 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 * `ActiveRecord::Tasks::MySQLDatabaseTasks` fails if shellout to
mysql commands (like `mysqldump`) is not successful. mysql commands (like `mysqldump`) is not successful.
......
...@@ -23,6 +23,20 @@ def table_alias_for(table_name) ...@@ -23,6 +23,20 @@ def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_') table_name[0...table_alias_length].tr('.', '_')
end 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. # Returns an array of table names defined in the database.
def tables(name = nil) def tables(name = nil)
raise NotImplementedError, "#tables is not implemented" raise NotImplementedError, "#tables is not implemented"
......
...@@ -628,6 +628,7 @@ def collation ...@@ -628,6 +628,7 @@ def collation
def tables(name = nil) # :nodoc: def tables(name = nil) # :nodoc:
select_values("SHOW FULL TABLES", 'SCHEMA') select_values("SHOW FULL TABLES", 'SCHEMA')
end end
alias data_sources tables
def truncate(table_name, name = nil) def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
...@@ -644,6 +645,7 @@ def table_exists?(table_name) ...@@ -644,6 +645,7 @@ def table_exists?(table_name)
select_values(sql, 'SCHEMA').any? select_values(sql, 'SCHEMA').any?
end end
alias data_source_exists? table_exists?
def views # :nodoc: def views # :nodoc:
select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA') select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
......
...@@ -73,6 +73,16 @@ def tables(name = nil) ...@@ -73,6 +73,16 @@ def tables(name = nil)
select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA') select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end 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. # Returns true if table exists.
# If the schema is not specified as part of +name+ then it will only find tables within # 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) # the current schema search path (regardless of permissions to access tables in other schemas)
...@@ -89,6 +99,7 @@ def table_exists?(name) ...@@ -89,6 +99,7 @@ def table_exists?(name)
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'} AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL SQL
end end
alias data_source_exists? table_exists?
def views # :nodoc: def views # :nodoc:
select_values(<<-SQL, 'SCHEMA') select_values(<<-SQL, 'SCHEMA')
......
...@@ -10,7 +10,7 @@ def initialize(conn) ...@@ -10,7 +10,7 @@ def initialize(conn)
@columns = {} @columns = {}
@columns_hash = {} @columns_hash = {}
@primary_keys = {} @primary_keys = {}
@tables = {} @data_sources = {}
end end
def initialize_dup(other) def initialize_dup(other)
...@@ -18,33 +18,38 @@ def initialize_dup(other) ...@@ -18,33 +18,38 @@ def initialize_dup(other)
@columns = @columns.dup @columns = @columns.dup
@columns_hash = @columns_hash.dup @columns_hash = @columns_hash.dup
@primary_keys = @primary_keys.dup @primary_keys = @primary_keys.dup
@tables = @tables.dup @data_sources = @data_sources.dup
end end
def primary_keys(table_name) 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 end
# A cached lookup for table existence. # A cached lookup for table existence.
def table_exists?(name) def data_source_exists?(name)
prepare_tables if @tables.empty? prepare_data_sources if @data_sources.empty?
return @tables[name] if @tables.key? name return @data_sources[name] if @data_sources.key? name
@tables[name] = connection.table_exists?(name) @data_sources[name] = connection.data_source_exists?(name)
end end
alias table_exists? data_source_exists?
deprecate :table_exists? => "use #data_source_exists? instead"
# Add internal cache for table with +table_name+. # Add internal cache for table with +table_name+.
def add(table_name) def add(table_name)
if table_exists?(table_name) if data_source_exists?(table_name)
primary_keys(table_name) primary_keys(table_name)
columns(table_name) columns(table_name)
columns_hash(table_name) columns_hash(table_name)
end end
end end
def tables(name) def data_sources(name)
@tables[name] @data_sources[name]
end end
alias tables data_sources
deprecate :tables => "use #data_sources instead"
# Get the columns for a table # Get the columns for a table
def columns(table_name) def columns(table_name)
...@@ -64,36 +69,38 @@ def clear! ...@@ -64,36 +69,38 @@ def clear!
@columns.clear @columns.clear
@columns_hash.clear @columns_hash.clear
@primary_keys.clear @primary_keys.clear
@tables.clear @data_sources.clear
@version = nil @version = nil
end end
def size def size
[@columns, @columns_hash, @primary_keys, @tables].map(&:size).inject :+ [@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+
end end
# Clear out internal caches for table with +table_name+. # Clear out internal caches for the data source +name+.
def clear_table_cache!(table_name) def clear_data_source_cache!(name)
@columns.delete table_name @columns.delete name
@columns_hash.delete table_name @columns_hash.delete name
@primary_keys.delete table_name @primary_keys.delete name
@tables.delete table_name @data_sources.delete name
end end
alias clear_table_cache! clear_data_source_cache!
deprecate :clear_table_cache! => "use #clear_data_source_cache! instead"
def marshal_dump def marshal_dump
# if we get current version during initialization, it happens stack over flow. # if we get current version during initialization, it happens stack over flow.
@version = ActiveRecord::Migrator.current_version @version = ActiveRecord::Migrator.current_version
[@version, @columns, @columns_hash, @primary_keys, @tables] [@version, @columns, @columns_hash, @primary_keys, @data_sources]
end end
def marshal_load(array) def marshal_load(array)
@version, @columns, @columns_hash, @primary_keys, @tables = array @version, @columns, @columns_hash, @primary_keys, @data_sources = array
end end
private private
def prepare_tables def prepare_data_sources
connection.tables.each { |table| @tables[table] = true } connection.data_sources.each { |source| @data_sources[source] = true }
end end
end end
end end
......
...@@ -319,10 +319,12 @@ def tables(name = nil, table_name = nil) #:nodoc: ...@@ -319,10 +319,12 @@ def tables(name = nil, table_name = nil) #:nodoc:
row['name'] row['name']
end end
end end
alias data_sources tables
def table_exists?(table_name) def table_exists?(table_name)
table_name && tables(nil, table_name).any? table_name && tables(nil, table_name).any?
end end
alias data_source_exists? table_exists?
def views # :nodoc: def views # :nodoc:
select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA') select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')
......
...@@ -213,7 +213,7 @@ def sequence_name=(value) ...@@ -213,7 +213,7 @@ def sequence_name=(value)
# Indicates whether the table associated with this class exists # Indicates whether the table associated with this class exists
def table_exists? def table_exists?
connection.schema_cache.table_exists?(table_name) connection.schema_cache.data_source_exists?(table_name)
end end
def attributes_builder # :nodoc: def attributes_builder # :nodoc:
...@@ -290,7 +290,7 @@ def content_columns ...@@ -290,7 +290,7 @@ def content_columns
def reset_column_information def reset_column_information
connection.clear_cache! connection.clear_cache!
undefine_attribute_methods undefine_attribute_methods
connection.schema_cache.clear_table_cache!(table_name) connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache reload_schema_from_cache
end end
......
...@@ -255,7 +255,7 @@ db_namespace = namespace :db do ...@@ -255,7 +255,7 @@ db_namespace = namespace :db do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
con.schema_cache.clear! 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)) } open(filename, 'wb') { |f| f.write(Marshal.dump(con.schema_cache)) }
end end
......
...@@ -89,7 +89,7 @@ def extensions(stream) ...@@ -89,7 +89,7 @@ def extensions(stream)
end end
def tables(stream) def tables(stream)
sorted_tables = @connection.tables.sort - @connection.views sorted_tables = @connection.data_sources.sort - @connection.views
sorted_tables.each do |table_name| sorted_tables.each do |table_name|
table(table_name, stream) unless ignored?(table_name) table(table_name, stream) unless ignored?(table_name)
......
...@@ -20,7 +20,7 @@ def type_cast_for_database(attribute_name, value) ...@@ -20,7 +20,7 @@ def type_cast_for_database(attribute_name, value)
private private
def column_for(attribute_name) 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] connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
end end
end end
......
...@@ -7,7 +7,7 @@ def fake_connection(config) ...@@ -7,7 +7,7 @@ def fake_connection(config)
module ConnectionAdapters module ConnectionAdapters
class FakeAdapter < AbstractAdapter class FakeAdapter < AbstractAdapter
attr_accessor :tables, :primary_keys attr_accessor :data_sources, :primary_keys
@columns = Hash.new { |h,k| h[k] = [] } @columns = Hash.new { |h,k| h[k] = [] }
class << self class << self
...@@ -16,7 +16,7 @@ class << self ...@@ -16,7 +16,7 @@ class << self
def initialize(connection, logger) def initialize(connection, logger)
super super
@tables = [] @data_sources = []
@primary_keys = {} @primary_keys = {}
@columns = self.class.columns @columns = self.class.columns
end end
...@@ -37,7 +37,7 @@ def columns(table_name) ...@@ -37,7 +37,7 @@ def columns(table_name)
@columns[table_name] @columns[table_name]
end end
def table_exists?(*) def data_source_exists?(*)
true true
end end
......
...@@ -36,6 +36,21 @@ def test_table_exists? ...@@ -36,6 +36,21 @@ def test_table_exists?
assert !@connection.table_exists?(nil) assert !@connection.table_exists?(nil)
end 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 def test_indexes
idx_name = "accounts_idx" idx_name = "accounts_idx"
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
module PGSchemaHelper module PGSchemaHelper
def with_schema_search_path(schema_search_path) def with_schema_search_path(schema_search_path)
@connection.schema_search_path = schema_search_path @connection.schema_search_path = schema_search_path
@connection.schema_cache.clear!
yield if block_given? yield if block_given?
ensure ensure
@connection.schema_search_path = "'$user', public" @connection.schema_search_path = "'$user', public"
@connection.schema_cache.clear!
end end
end end
......
...@@ -29,7 +29,7 @@ def test_caches_columns_hash ...@@ -29,7 +29,7 @@ def test_caches_columns_hash
def test_clearing def test_clearing
@cache.columns('posts') @cache.columns('posts')
@cache.columns_hash('posts') @cache.columns_hash('posts')
@cache.tables('posts') @cache.data_sources('posts')
@cache.primary_keys('posts') @cache.primary_keys('posts')
@cache.clear! @cache.clear!
...@@ -40,17 +40,22 @@ def test_clearing ...@@ -40,17 +40,22 @@ def test_clearing
def test_dump_and_load def test_dump_and_load
@cache.columns('posts') @cache.columns('posts')
@cache.columns_hash('posts') @cache.columns_hash('posts')
@cache.tables('posts') @cache.data_sources('posts')
@cache.primary_keys('posts') @cache.primary_keys('posts')
@cache = Marshal.load(Marshal.dump(@cache)) @cache = Marshal.load(Marshal.dump(@cache))
assert_equal 11, @cache.columns('posts').size assert_equal 11, @cache.columns('posts').size
assert_equal 11, @cache.columns_hash('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') assert_equal 'id', @cache.primary_keys('posts')
end 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 end
end end
...@@ -44,9 +44,15 @@ def test_view_exists ...@@ -44,9 +44,15 @@ def test_view_exists
def test_table_exists def test_table_exists
view_name = Ebook.table_name 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" assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
end 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 def test_column_definitions
assert_equal([["id", :integer], assert_equal([["id", :integer],
["name", :string], ["name", :string],
......
...@@ -3,7 +3,7 @@ def self.extended(base) ...@@ -3,7 +3,7 @@ def self.extended(base)
base.class_eval do base.class_eval do
establish_connection(:adapter => 'fake') establish_connection(:adapter => 'fake')
connection.tables = [table_name] connection.data_sources = [table_name]
connection.primary_keys = { connection.primary_keys = {
table_name => 'id' table_name => 'id'
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册