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
|
# 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
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
'Cloud SQL for PostgreSQL' => { statement: 'SHOW cloudsql.iam_authentication', error: /PG::UndefinedObject/ },
# 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.
'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%'" }
}.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
private
def connection
model.connection
end
end
end
end
|