diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
13 files changed, 271 insertions, 74 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..e196df079b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -346,7 +346,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/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 51a6929dab..00383bad3b 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) @@ -826,8 +841,8 @@ 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) 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 1b9f865666..7768c24967 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -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..049768effc 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -13,41 +13,137 @@ module ActiveRecord @config = original.config.dup end - ## - # Builds a ConnectionSpecification from user input - class Resolver # :nodoc: - attr_reader :config, :klass, :configurations - - def initialize(config, configurations) - @config = config - @configurations = configurations + # 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 - 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 - 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 resolve_string_connection(spec) # :nodoc: - hash = configurations.fetch(spec) do |k| - connection_url_to_hash(k) + + 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 - raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash + ## + # Builds a ConnectionSpecification from user input. + class Resolver # :nodoc: + attr_reader :configurations - resolve_hash_connection hash + # 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 + + # 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 defined?(Rails.env) + resolve_env_connection Rails.env.to_sym + else + raise AdapterNotSpecified + end 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 +157,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") 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 760f1435eb..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 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_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d23a24589c..11a5eba464 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 @@ -865,6 +873,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. @@ -969,12 +983,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 + def create_table_definition(name, temporary, options, as = nil) + TableDefinition.new native_database_types, name, temporary, options, as end def update_table_definition(table_name, base) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index a02eda5603..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 |