cartodb/lib/carto/db/connection.rb
2020-06-15 10:58:47 +08:00

140 lines
5.2 KiB
Ruby

module Carto
module Db
class InvalidConfiguration < RuntimeError; end
class Connection
SCHEMA_PUBLIC = 'public'.freeze
SCHEMA_CARTODB = 'cartodb'.freeze
SCHEMA_IMPORTER = 'cdb_importer'.freeze
SCHEMA_GEOCODING = 'cdb'.freeze
SCHEMA_CDB_DATASERVICES_API = 'cdb_dataservices_client'.freeze
SCHEMA_AGGREGATION_TABLES = 'aggregation'.freeze
class << self
def connect(db_host, db_name, options = {})
validate_options(options)
if options[:statement_timeout]
filtered_options = options.reject { |k, _| k == :statement_timeout }
_, conn = connect(db_host, db_name, filtered_options)
conn.execute(%{ SET statement_timeout TO #{options[:statement_timeout]} })
end
configuration = get_db_configuration_for(db_host, db_name, options)
conn = $pool.fetch(configuration) do
get_database(options, configuration)
end
database = Carto::Db::Database.new(db_host, conn)
if block_given?
yield(database, conn)
else
return database, conn
end
ensure
if options[:statement_timeout]
filtered_options = options.reject { |k, _| k == :statement_timeout }
_, conn = connect(db_host, db_name, filtered_options)
conn.execute(%{ SET statement_timeout TO DEFAULT })
end
end
private
def get_database(options, configuration)
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new([])
conn = ActiveRecord::Base.connection_handler.establish_connection(
get_connection_name(options[:as]), resolver.spec(configuration)
).connection
# TODO: Maybe we should avoid doing this kind of operations here or remove it because
# all internal calls to functions should use the schema name like if we were using
# namespaces to avoid collisions and problems
if options[:as] != :cluster_admin
conn.execute(%{ SET search_path TO #{build_search_path(options[:user_schema])} })
end
conn
end
def build_search_path(user_schema, quote_user_schema = true)
quote_char = quote_user_schema ? "\"" : ""
"#{quote_char}#{user_schema}#{quote_char}, #{SCHEMA_CARTODB}, #{SCHEMA_CDB_DATASERVICES_API}, #{SCHEMA_PUBLIC}"
end
class NamedThing
def initialize(name)
@name = name
end
attr_reader :name
end
def get_connection_name(kind = :carto_db_connection)
NamedThing.new(kind.to_s)
end
def get_db_configuration_for(db_host, db_name, options)
logger = (Rails.env.development? || Rails.env.test? ? ::Rails.logger : nil)
# TODO: proper AR config when migration is complete
base_config = ::SequelRails.configuration.environment_for(Rails.env)
config = {
orm: 'ar',
adapter: "postgresql",
logger: logger,
host: db_host,
username: base_config['username'],
password: base_config['password'],
database: db_name,
port: base_config['port'],
encoding: base_config['encoding'].nil? ? 'unicode' : base_config['encoding'],
connect_timeout: base_config['connect_timeout']
}
case options[:as]
when :superuser
config
when :cluster_admin
config.merge(
database: 'postgres'
)
when :public_user
config.merge(
username: CartoDB::PUBLIC_DB_USER,
password: CartoDB::PUBLIC_DB_USER_PASSWORD
)
when :public_db_user
config.merge(
username: options[:username],
password: CartoDB::PUBLIC_DB_USER_PASSWORD
)
else
config.merge(
username: options[:username],
password: options[:password]
)
end
end
def validate_options(options)
if !options[:user_schema] && options[:as] != :cluster_admin
CartoDB::Logger.error(message: 'Connection needs user schema if the user is not the cluster admin',
params: { user_type: options[:as],
username: options[:username],
schema: options[:user_schema] })
raise Carto::Db::InvalidConfiguration.new('Connection needs user schema if user is not the cluster admin')
end
## If we don't pass user type we are using a regular user and username/password is mandatory
if !options[:as] && (!options[:username] || !options[:password])
CartoDB::Logger.error(message: 'Db connection needs username/password for regular user',
params: { user_type: options[:as], username: options[:username] })
raise Carto::Db::InvalidConfiguration.new('Db connection needs user username/password for regular user')
end
end
end
end
end
end