aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb243
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb86
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb12
17 files changed, 345 insertions, 118 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index cfdcae7f63..cebe741daa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -86,7 +86,7 @@ module ActiveRecord
end
end
- # Return the number of threads currently waiting on this
+ # Returns the number of threads currently waiting on this
# queue.
def num_waiting
synchronize do
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 1a323b6a9c..c90915c509 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -286,10 +286,6 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixture(fixture, table_name)
- execute fixture_sql(fixture, table_name), 'Fixture Insert'
- end
-
- def fixture_sql(fixture, table_name)
columns = schema_cache.columns_hash(table_name)
key_list = []
@@ -298,7 +294,7 @@ module ActiveRecord
quote(value, columns[name])
end
- "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})"
+ execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
end
def empty_insert_statement_value
@@ -346,7 +342,7 @@ module ActiveRecord
protected
- # Return a subquery for the given key using the join information.
+ # Returns a subquery for the given key using the join information.
def subquery_for(key, select)
subselect = select.clone
subselect.projections = [key]
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 8399232d73..adc23a6674 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -31,8 +31,8 @@ module ActiveRecord
old, @query_cache_enabled = @query_cache_enabled, true
yield
ensure
- clear_query_cache
@query_cache_enabled = old
+ clear_query_cache unless @query_cache_enabled
end
def enable_query_cache!
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 552a22d28a..75501852ed 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -43,7 +43,9 @@ module ActiveRecord
# SQLite does not understand dates, so this method will convert a Date
# to a String.
def type_cast(value, column)
- return value.id if value.respond_to?(:quoted_id)
+ if value.respond_to?(:quoted_id) && value.respond_to?(:id)
+ return value.id
+ end
case value
when String, ActiveSupport::Multibyte::Chars
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 7c330a2f25..a51691bfa8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -34,9 +34,10 @@ module ActiveRecord
def visit_TableDefinition(o)
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
- create_sql << "#{quote_table_name(o.name)} ("
- create_sql << o.columns.map { |c| accept c }.join(', ')
- create_sql << ") #{o.options}"
+ create_sql << "#{quote_table_name(o.name)} "
+ create_sql << "(#{o.columns.map { |c| accept c }.join(', ')}) " unless o.as
+ create_sql << "#{o.options}"
+ create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
create_sql
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 063b19871a..c39bf15e83 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -49,14 +49,15 @@ module ActiveRecord
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :indexes
- attr_reader :name, :temporary, :options
+ attr_reader :name, :temporary, :options, :as
- def initialize(types, name, temporary, options)
+ def initialize(types, name, temporary, options, as = nil)
@columns_hash = {}
@indexes = {}
@native = types
@temporary = temporary
@options = options
+ @as = as
@name = name
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 6268ae4875..88bf15bc18 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -131,6 +131,9 @@ module ActiveRecord
# [<tt>:force</tt>]
# Set to true to drop the table before creating it.
# Defaults to false.
+ # [<tt>:as</tt>]
+ # SQL to use to generate the table. When this option is used, the block is
+ # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
#
# ====== Add a backend specific option to the generated SQL (MySQL)
#
@@ -169,19 +172,31 @@ module ActiveRecord
# supplier_id int
# )
#
+ # ====== Create a temporary table based on a query
+ #
+ # create_table(:long_query, temporary: true,
+ # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
+ #
+ # generates:
+ #
+ # CREATE TEMPORARY TABLE long_query AS
+ # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
+ #
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options]
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
- unless options[:id] == false
- pk = options.fetch(:primary_key) {
- Base.get_primary_key table_name.to_s.singularize
- }
+ if !options[:as]
+ unless options[:id] == false
+ pk = options.fetch(:primary_key) {
+ Base.get_primary_key table_name.to_s.singularize
+ }
- td.primary_key pk, options.fetch(:id, :primary_key), options
- end
+ td.primary_key pk, options.fetch(:id, :primary_key), options
+ end
- yield td if block_given?
+ yield td if block_given?
+ end
if options[:force] && table_exists?(table_name)
drop_table(table_name, options)
@@ -690,7 +705,7 @@ module ActiveRecord
column_type_sql
else
- type
+ type.to_s
end
end
@@ -699,7 +714,7 @@ module ActiveRecord
# require the order columns appear in the SELECT.
#
# columns_for_distinct("posts.id", ["posts.created_at desc"])
- def columns_for_distinct(columns, orders) # :nodoc:
+ def columns_for_distinct(columns, orders) #:nodoc:
columns
end
@@ -721,6 +736,10 @@ module ActiveRecord
remove_column table_name, :created_at
end
+ def update_table_definition(table_name, base) #:nodoc:
+ Table.new(table_name, base)
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -826,17 +845,13 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary, options)
- TableDefinition.new native_database_types, name, temporary, options
+ def create_table_definition(name, temporary, options, as = nil)
+ TableDefinition.new native_database_types, name, temporary, options, as
end
def create_alter_table(name)
AlterTable.new create_table_definition(name, false, {})
end
-
- def update_table_definition(table_name, base)
- Table.new(table_name, base)
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index e3270e734f..7768c24967 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -148,7 +148,7 @@ module ActiveRecord
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
NATIVE_DATABASE_TYPES = {
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
+ :primary_key => "int(11) auto_increment PRIMARY KEY",
:string => { :name => "varchar", :limit => 255 },
:text => { :name => "text" },
:integer => { :name => "int", :limit => 4 },
@@ -303,12 +303,6 @@ module ActiveRecord
else
log(sql, name) { @connection.query(sql) }
end
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid.new("'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings.", exception.original_exception)
- else
- raise
- end
end
# MysqlAdapter has to free a result after using it, so we use this method to write
@@ -492,6 +486,10 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
+ def drop_table(table_name, options = {})
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
+ end
+
def rename_index(table_name, old_name, new_name)
if (version[0] == 5 && version[1] >= 7) || version[0] >= 6
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 64fc9e95d8..3f8b14bf67 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -13,41 +13,146 @@ module ActiveRecord
@config = original.config.dup
end
+ # Expands a connection string into a hash.
+ class ConnectionUrlResolver # :nodoc:
+
+ # == Example
+ #
+ # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
+ # ConnectionUrlResolver.new(url).to_hash
+ # # => {
+ # "adapter" => "postgresql",
+ # "host" => "localhost",
+ # "port" => 9000,
+ # "database" => "foo_test",
+ # "username" => "foo",
+ # "password" => "bar",
+ # "pool" => "5",
+ # "timeout" => "3000"
+ # }
+ def initialize(url)
+ raise "Database URL cannot be empty" if url.blank?
+ @uri = URI.parse(url)
+ @adapter = @uri.scheme
+ @adapter = "postgresql" if @adapter == "postgres"
+ @query = @uri.query || ''
+ end
+
+ # Converts the given URL to a full connection hash.
+ def to_hash
+ config = raw_config.reject { |_,value| value.blank? }
+ config.map { |key,value| config[key] = uri_parser.unescape(value) if value.is_a? String }
+ config
+ end
+
+ private
+
+ def uri
+ @uri
+ end
+
+ def uri_parser
+ @uri_parser ||= URI::Parser.new
+ end
+
+ # Converts the query parameters of the URI into a hash.
+ #
+ # "localhost?pool=5&reap_frequency=2"
+ # # => { "pool" => "5", "reap_frequency" => "2" }
+ #
+ # returns empty hash if no query present.
+ #
+ # "localhost"
+ # # => {}
+ def query_hash
+ Hash[@query.split("&").map { |pair| pair.split("=") }]
+ end
+
+ def raw_config
+ query_hash.merge({
+ "adapter" => @adapter,
+ "username" => uri.user,
+ "password" => uri.password,
+ "port" => uri.port,
+ "database" => database,
+ "host" => uri.host })
+ end
+
+ # Returns name of the database.
+ # Sqlite3 expects this to be a full path or `:memory:`.
+ def database
+ if @adapter == 'sqlite3'
+ if '/:memory:' == uri.path
+ ':memory:'
+ else
+ uri.path
+ end
+ else
+ uri.path.sub(%r{^/},"")
+ end
+ end
+ end
+
##
- # Builds a ConnectionSpecification from user input
+ # Builds a ConnectionSpecification from user input.
class Resolver # :nodoc:
- attr_reader :config, :klass, :configurations
+ attr_reader :configurations
- def initialize(config, configurations)
- @config = config
+ # Accepts a hash two layers deep, keys on the first layer represent
+ # environments such as "production". Keys must be strings.
+ def initialize(configurations)
@configurations = configurations
end
- def spec
- case config
- when nil
- raise AdapterNotSpecified unless defined?(Rails.env)
- resolve_string_connection Rails.env
- when Symbol, String
- resolve_string_connection config.to_s
- when Hash
- resolve_hash_connection config
+ # Returns a hash with database connection information.
+ #
+ # == Examples
+ #
+ # Full hash Configuration.
+ #
+ # configurations = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
+ # Resolver.new(configurations).resolve(:production)
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3"}
+ #
+ # Initialized with URL configuration strings.
+ #
+ # configurations = { "production" => "postgresql://localhost/foo" }
+ # Resolver.new(configurations).resolve(:production)
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve(config)
+ if config
+ resolve_connection config
+ elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
+ resolve_env_connection env.to_sym
+ else
+ raise AdapterNotSpecified
end
end
- private
- def resolve_string_connection(spec) # :nodoc:
- hash = configurations.fetch(spec) do |k|
- connection_url_to_hash(k)
+ # Expands each key in @configurations hash into fully resolved hash
+ def resolve_all
+ config = configurations.dup
+ config.each do |key, value|
+ config[key] = resolve(value) if value
end
-
- raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
-
- resolve_hash_connection hash
+ config
end
- def resolve_hash_connection(spec) # :nodoc:
- spec = spec.symbolize_keys
+ # Returns an instance of ConnectionSpecification for a given adapter.
+ # Accepts a hash one layer deep that contains all connection information.
+ #
+ # == Example
+ #
+ # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
+ # spec = Resolver.new(config).spec(:production)
+ # spec.adapter_method
+ # # => "sqlite3"
+ # spec.config
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
+ #
+ def spec(config)
+ spec = resolve(config).symbolize_keys
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
@@ -61,35 +166,87 @@ module ActiveRecord
end
adapter_method = "#{spec[:adapter]}_connection"
-
ConnectionSpecification.new(spec, adapter_method)
end
- def connection_url_to_hash(url) # :nodoc:
- config = URI.parse url
- adapter = config.scheme
- adapter = "postgresql" if adapter == "postgres"
- spec = { :adapter => adapter,
- :username => config.user,
- :password => config.password,
- :port => config.port,
- :database => config.path.sub(%r{^/},""),
- :host => config.host }
-
- spec.reject!{ |_,value| value.blank? }
-
- uri_parser = URI::Parser.new
-
- spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
+ private
- if config.query
- options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
+ # Returns fully resolved connection, accepts hash, string or symbol.
+ # Always returns a hash.
+ #
+ # == Examples
+ #
+ # Symbol representing current environment.
+ #
+ # Resolver.new("production" => {}).resolve_connection(:production)
+ # # => {}
+ #
+ # One layer deep hash of connection values.
+ #
+ # Resolver.new({}).resolve_connection("adapter" => "sqlite3")
+ # # => { "adapter" => "sqlite3" }
+ #
+ # Connection URL.
+ #
+ # Resolver.new({}).resolve_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve_connection(spec)
+ case spec
+ when Symbol, String
+ resolve_env_connection spec
+ when Hash
+ resolve_hash_connection spec
+ end
+ end
- spec.merge!(options)
+ # Takes the environment such as `:production` or `:development`.
+ # This requires that the @configurations was initialized with a key that
+ # matches.
+ #
+ #
+ # Resolver.new("production" => {}).resolve_env_connection(:production)
+ # # => {}
+ #
+ # Takes a connection URL.
+ #
+ # Resolver.new({}).resolve_env_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve_env_connection(spec)
+ # Rails has historically accepted a string to mean either
+ # an environment key or a URL spec, so we have deprecated
+ # this ambiguous behaviour and in the future this function
+ # can be removed in favor of resolve_string_connection and
+ # resolve_symbol_connection.
+ if config = configurations[spec.to_s]
+ if spec.is_a?(String)
+ ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \
+ "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead"
+ end
+ resolve_connection(config)
+ elsif spec.is_a?(String)
+ resolve_string_connection(spec)
+ else
+ raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}")
end
+ end
+ # Accepts a hash. Expands the "url" key that contains a
+ # URL database connection to a full connection
+ # hash and merges with the rest of the hash.
+ # Connection details inside of the "url" key win any merge conflicts
+ def resolve_hash_connection(spec)
+ if url = spec.delete("url")
+ connection_hash = resolve_string_connection(url)
+ spec.merge!(connection_hash)
+ end
spec
end
+
+ def resolve_string_connection(url)
+ ConnectionUrlResolver.new(url).to_hash
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index e790f731ea..6d8e994654 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -18,6 +18,12 @@ module ActiveRecord
client = Mysql2::Client.new(config)
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
+ rescue Mysql2::Error => error
+ if error.message.include?("Unknown database")
+ raise ActiveRecord::NoDatabaseError.new(error.message)
+ else
+ raise error
+ end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index c4dcba0501..7dbaa272a3 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -34,6 +34,12 @@ module ActiveRecord
default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
options = [host, username, password, database, port, socket, default_flags]
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
+ rescue Mysql::Error => error
+ if error.message.include?("Unknown database")
+ raise ActiveRecord::NoDatabaseError.new(error.message)
+ else
+ raise error
+ end
end
end
@@ -419,14 +425,19 @@ module ActiveRecord
if result
types = {}
+ fields = []
result.fetch_fields.each { |field|
+ field_name = field.name
+ fields << field_name
+
if field.decimals > 0
- types[field.name] = Fields::Decimal.new
+ types[field_name] = Fields::Decimal.new
else
- types[field.name] = Fields.find_type field
+ types[field_name] = Fields.find_type field
end
}
- result_set = ActiveRecord::Result.new(types.keys, result.to_a, types)
+
+ result_set = ActiveRecord::Result.new(fields, result.to_a, types)
result.free
else
result_set = ActiveRecord::Result.new([], [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index bf34f2bdae..35ce881302 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -144,7 +144,7 @@ module ActiveRecord
def quote_and_escape(value)
case value
- when "NULL"
+ when "NULL", Numeric
value
else
"\"#{value.gsub(/"/,"\\\"")}\""
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index fa173d13a2..f349c37724 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -142,7 +142,7 @@ module ActiveRecord
fields.each_with_index do |fname, i|
ftype = result.ftype i
fmod = result.fmod i
- types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
+ types[fname] = type_map.fetch(ftype, fmod) { |oid, mod|
warn "unknown OID: #{fname}(#{oid}) (#{sql})"
OID::Identity.new
}
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 2a9547fd10..fae260a921 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -301,11 +301,9 @@ module ActiveRecord
end
end
- TYPE_MAP = TypeMap.new # :nodoc:
-
- # When the PG adapter connects, the pg_type table is queried. The
+ # When the PG adapter connects, the pg_type table is queried. The
# key of this hash maps to the `typname` column from the table.
- # TYPE_MAP is then dynamically built with oids as the key and type
+ # type_map is then dynamically built with oids as the key and type
# objects as values.
NAMES = Hash.new { |h,k| # :nodoc:
h[k] = OID::Identity.new
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 5dc70a5ad1..571257f6dd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -172,7 +172,7 @@ module ActiveRecord
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
- oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
+ oid = type_map.fetch(oid.to_i, fmod.to_i) {
OID::Identity.new
}
PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index adeb57d913..7e188907e1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -46,7 +46,7 @@ module ActiveRecord
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
attr_accessor :array
- # Instantiates a new PostgreSQL column definition in a table.
+
def initialize(name, default, oid_type, sql_type = nil, null = true)
@oid_type = oid_type
default_value = self.class.extract_value_from_default(default)
@@ -62,6 +62,14 @@ module ActiveRecord
@default_function = default if has_default_function?(default_value, default)
end
+ def number?
+ !array && super
+ end
+
+ def text?
+ !array && super
+ end
+
# :stopdoc:
class << self
include ConnectionAdapters::PostgreSQLColumn::Cast
@@ -565,7 +573,8 @@ module ActiveRecord
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
end
- initialize_type_map
+ @type_map = OID::TypeMap.new
+ initialize_type_map(type_map)
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
end
@@ -712,6 +721,10 @@ module ActiveRecord
!native_database_types[type].nil?
end
+ def update_table_definition(table_name, base) #:nodoc:
+ Table.new(table_name, base)
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
@@ -738,43 +751,60 @@ module ActiveRecord
private
+ def type_map
+ @type_map
+ end
+
def reload_type_map
- OID::TYPE_MAP.clear
- initialize_type_map
+ type_map.clear
+ initialize_type_map(type_map)
+ end
+
+ def add_oid(row, records_by_oid, type_map)
+ return type_map if type_map.key? row['type_elem'].to_i
+
+ if OID.registered_type? row['typname']
+ # this composite type is explicitly registered
+ vector = OID::NAMES[row['typname']]
+ else
+ # use the default for composite types
+ unless type_map.key? row['typelem'].to_i
+ add_oid records_by_oid[row['typelem']], records_by_oid, type_map
+ end
+
+ vector = OID::Vector.new row['typdelim'], type_map[row['typelem'].to_i]
+ end
+
+ type_map[row['oid'].to_i] = vector
+ type_map
end
- def initialize_type_map
+ def initialize_type_map(type_map)
result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
leaves, nodes = result.partition { |row| row['typelem'] == '0' }
# populate the leaf nodes
leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
- OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
+ type_map[row['oid'].to_i] = OID::NAMES[row['typname']]
end
+ records_by_oid = result.group_by { |row| row['oid'] }
+
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
# populate composite types
- nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- if OID.registered_type? row['typname']
- # this composite type is explicitly registered
- vector = OID::NAMES[row['typname']]
- else
- # use the default for composite types
- vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
- end
-
- OID::TYPE_MAP[row['oid'].to_i] = vector
+ nodes.each do |row|
+ add_oid row, records_by_oid, type_map
end
# populate array types
- arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
- OID::TYPE_MAP[row['oid'].to_i] = array
+ arrays.find_all { |row| type_map.key? row['typelem'].to_i }.each do |row|
+ array = OID::Array.new type_map[row['typelem'].to_i]
+ type_map[row['oid'].to_i] = array
end
end
- FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
+ FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
def exec_no_cache(sql, name, binds)
log(sql, name, binds) { @connection.async_exec(sql) }
@@ -847,6 +877,12 @@ module ActiveRecord
PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
configure_connection
+ rescue ::PG::Error => error
+ if error.message.include?("does not exist")
+ raise ActiveRecord::NoDatabaseError.new(error.message)
+ else
+ raise error
+ end
end
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
@@ -951,16 +987,12 @@ module ActiveRecord
end
def extract_table_ref_from_insert_sql(sql)
- sql[/into\s+([^\(]*).*values\s*\(/i]
+ sql[/into\s+([^\(]*).*values\s*\(/im]
$1.strip if $1
end
- def create_table_definition(name, temporary, options)
- TableDefinition.new native_database_types, name, temporary, options
- end
-
- def update_table_definition(table_name, base)
- Table.new(table_name, base)
+ def create_table_definition(name, temporary, options, as = nil)
+ TableDefinition.new native_database_types, name, temporary, options, as
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 2cf1015f2c..92bb70ba53 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -31,6 +31,12 @@ module ActiveRecord
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
+ rescue Errno::ENOENT => error
+ if error.message.include?("No such file or directory")
+ raise ActiveRecord::NoDatabaseError.new(error.message)
+ else
+ raise error
+ end
end
end
@@ -586,7 +592,11 @@ module ActiveRecord
def translate_exception(exception, message)
case exception.message
- when /column(s)? .* (is|are) not unique/
+ # SQLite 3.8.2 returns a newly formatted error message:
+ # UNIQUE constraint failed: *table_name*.*column_name*
+ # Older versions of SQLite return:
+ # column *column_name* is not unique
+ when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
RecordNotUnique.new(message, exception)
else
super