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:
parent
1165e9c898
commit
152b85f06c
16 changed files with 108 additions and 34 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue