2021-11-15 10:10:57 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module Database
|
|
|
|
# A class for reflecting upon a database and its settings, such as the
|
|
|
|
# adapter name, PostgreSQL version, and the presence of tables or columns.
|
|
|
|
class Reflection
|
|
|
|
attr_reader :model
|
|
|
|
|
|
|
|
def initialize(model)
|
|
|
|
@model = model
|
|
|
|
@version = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def config
|
|
|
|
# The result of this method must not be cached, as other methods may use
|
|
|
|
# it after making configuration changes and expect those changes to be
|
|
|
|
# present. For example, `disable_prepared_statements` expects the
|
|
|
|
# configuration settings to always be up to date.
|
|
|
|
#
|
|
|
|
# See the following for more information:
|
|
|
|
#
|
|
|
|
# - https://gitlab.com/gitlab-org/release/retrospectives/-/issues/39
|
|
|
|
# - https://gitlab.com/gitlab-com/gl-infra/production/-/issues/5238
|
|
|
|
model.connection_db_config.configuration_hash.with_indifferent_access
|
|
|
|
end
|
|
|
|
|
|
|
|
def username
|
|
|
|
config[:username] || ENV['USER']
|
|
|
|
end
|
|
|
|
|
|
|
|
def database_name
|
|
|
|
config[:database]
|
|
|
|
end
|
|
|
|
|
|
|
|
def adapter_name
|
|
|
|
config[:adapter]
|
|
|
|
end
|
|
|
|
|
|
|
|
def human_adapter_name
|
|
|
|
if postgresql?
|
|
|
|
'PostgreSQL'
|
|
|
|
else
|
|
|
|
'Unknown'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def postgresql?
|
|
|
|
adapter_name.casecmp('postgresql') == 0
|
|
|
|
end
|
|
|
|
|
|
|
|
# Check whether the underlying database is in read-only mode
|
|
|
|
def db_read_only?
|
|
|
|
pg_is_in_recovery =
|
|
|
|
connection
|
|
|
|
.execute('SELECT pg_is_in_recovery()')
|
|
|
|
.first
|
|
|
|
.fetch('pg_is_in_recovery')
|
|
|
|
|
|
|
|
Gitlab::Utils.to_boolean(pg_is_in_recovery)
|
|
|
|
end
|
|
|
|
|
|
|
|
def db_read_write?
|
|
|
|
!db_read_only?
|
|
|
|
end
|
|
|
|
|
|
|
|
def version
|
|
|
|
@version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
|
|
|
|
end
|
|
|
|
|
|
|
|
def database_version
|
|
|
|
connection.execute("SELECT VERSION()").first['version']
|
|
|
|
end
|
|
|
|
|
|
|
|
def postgresql_minimum_supported_version?
|
|
|
|
version.to_f >= MINIMUM_POSTGRES_VERSION
|
|
|
|
end
|
|
|
|
|
|
|
|
def cached_column_exists?(column_name)
|
|
|
|
connection
|
|
|
|
.schema_cache.columns_hash(model.table_name)
|
|
|
|
.has_key?(column_name.to_s)
|
|
|
|
end
|
|
|
|
|
|
|
|
def cached_table_exists?
|
|
|
|
exists? && connection.schema_cache.data_source_exists?(model.table_name)
|
|
|
|
end
|
|
|
|
|
|
|
|
def exists?
|
|
|
|
# We can't _just_ check if `connection` raises an error, as it will
|
|
|
|
# point to a `ConnectionProxy`, and obtaining those doesn't involve any
|
|
|
|
# database queries. So instead we obtain the database version, which is
|
|
|
|
# cached after the first call.
|
|
|
|
connection.schema_cache.database_version
|
|
|
|
true
|
|
|
|
rescue StandardError
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
def system_id
|
|
|
|
row = connection
|
|
|
|
.execute('SELECT system_identifier FROM pg_control_system()')
|
|
|
|
.first
|
|
|
|
|
|
|
|
row['system_identifier']
|
|
|
|
end
|
|
|
|
|
2021-12-19 22:12:58 -05:00
|
|
|
def flavor
|
|
|
|
{
|
|
|
|
# Based on https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/
|
|
|
|
'Amazon Aurora PostgreSQL' => { statement: 'SELECT AURORA_VERSION()', error: /PG::UndefinedFunction/ },
|
|
|
|
# Based on https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts.General.FeatureSupport.Extensions,
|
|
|
|
# this is also available for both Aurora and RDS, so we need to check for the former first.
|
|
|
|
'PostgreSQL on Amazon RDS' => { statement: 'SHOW rds.extensions', error: /PG::UndefinedObject/ },
|
|
|
|
# Based on https://cloud.google.com/sql/docs/postgres/flags#postgres-c this should be specific
|
|
|
|
# to Cloud SQL for PostgreSQL
|
2022-08-18 11:12:17 -04:00
|
|
|
'Cloud SQL for PostgreSQL' => { statement: 'SHOW cloudsql.iam_authentication', error: /PG::UndefinedObject/ },
|
2021-12-19 22:12:58 -05:00
|
|
|
# Based on
|
|
|
|
# - https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-extensions
|
|
|
|
# - https://docs.microsoft.com/en-us/azure/postgresql/concepts-extensions
|
|
|
|
# this should be available only for Azure Database for PostgreSQL - Flexible Server.
|
|
|
|
'Azure Database for PostgreSQL - Flexible Server' => { statement: 'SHOW azure.extensions', error: /PG::UndefinedObject/ },
|
|
|
|
# Based on
|
|
|
|
# - https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-servers
|
|
|
|
# - https://docs.microsoft.com/en-us/azure/postgresql/concepts-servers#managing-your-server
|
|
|
|
# this database is present on both Flexible and Single server, so we should check the former first.
|
2022-09-23 11:11:09 -04:00
|
|
|
'Azure Database for PostgreSQL - Single Server' => { statement: "SELECT datname FROM pg_database WHERE datname = 'azure_maintenance'" },
|
|
|
|
# Based on
|
|
|
|
# - https://cloud.google.com/sql/docs/postgres/flags
|
|
|
|
# running a query to detect flag names that begin with 'alloydb
|
|
|
|
'AlloyDB for PostgreSQL' => { statement: "SELECT name FROM pg_settings WHERE name LIKE 'alloydb%'" }
|
2021-12-19 22:12:58 -05:00
|
|
|
}.each do |flavor, conditions|
|
|
|
|
return flavor if connection.execute(conditions[:statement]).to_a.present?
|
|
|
|
rescue ActiveRecord::StatementInvalid => e
|
|
|
|
raise if conditions[:error] && !e.message.match?(conditions[:error])
|
|
|
|
end
|
|
|
|
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2021-11-15 10:10:57 -05:00
|
|
|
private
|
|
|
|
|
|
|
|
def connection
|
|
|
|
model.connection
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|