aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/connection_specification.rb
blob: a1664509df23dc4497b31d3bc3d9501e7728968f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
require 'uri'

module ActiveRecord
  module ConnectionAdapters
    class ConnectionSpecification #:nodoc:
      attr_reader :config, :adapter_method

      def initialize(config, adapter_method)
        @config, @adapter_method = config, adapter_method
      end

      def initialize_dup(original)
        @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
      class Resolver # :nodoc:
        attr_reader :configurations

        def initialize(configurations)
          @configurations = configurations
        end

        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 spec(config)
          spec = resolve(config).symbolize_keys

          raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)

          path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
          begin
            require path_to_adapter
          rescue Gem::LoadError => e
            raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)."
          rescue LoadError => e
            raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
          end

          adapter_method = "#{spec[:adapter]}_connection"
          ConnectionSpecification.new(spec, adapter_method)
        end

        private

        def resolve_connection(spec) #:nodoc:
          case spec
          when Symbol, String
            resolve_env_connection spec
          when Hash
            resolve_hash_connection spec
          end
        end

        def resolve_env_connection(spec) # :nodoc:
          # 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

        def resolve_hash_connection(spec) # :nodoc:
          spec
        end

        def resolve_string_connection(url) # :nodoc:
          ConnectionUrlResolver.new(url).to_hash
        end
      end
    end
  end
end