Class: Gitlab::Database::Reflection

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/database/reflection.rb

Overview

A class for reflecting upon a database and its settings, such as the adapter name, PostgreSQL version, and the presence of tables or columns.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model) ⇒ Reflection

Returns a new instance of Reflection.



10
11
12
13
# File 'lib/gitlab/database/reflection.rb', line 10

def initialize(model)
  @model = model
  @version = nil
end

Instance Attribute Details

#modelObject (readonly)

Returns the value of attribute model.



8
9
10
# File 'lib/gitlab/database/reflection.rb', line 8

def model
  @model
end

Instance Method Details

#adapter_nameObject



36
37
38
# File 'lib/gitlab/database/reflection.rb', line 36

def adapter_name
  config[:adapter]
end

#cached_column_exists?(column_name) ⇒ Boolean

Returns:

  • (Boolean)


79
80
81
82
83
# File 'lib/gitlab/database/reflection.rb', line 79

def cached_column_exists?(column_name)
  connection
    .schema_cache.columns_hash(model.table_name)
    .has_key?(column_name.to_s)
end

#cached_table_exists?Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/gitlab/database/reflection.rb', line 85

def cached_table_exists?
  exists? && connection.schema_cache.data_source_exists?(model.table_name)
end

#configObject



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/gitlab/database/reflection.rb', line 15

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

#database_nameObject



32
33
34
# File 'lib/gitlab/database/reflection.rb', line 32

def database_name
  config[:database]
end

#database_versionObject



71
72
73
# File 'lib/gitlab/database/reflection.rb', line 71

def database_version
  connection.execute("SELECT VERSION()").first['version']
end

#db_read_only?Boolean

Check whether the underlying database is in read-only mode

Returns:

  • (Boolean)


53
54
55
56
57
58
59
60
61
# File 'lib/gitlab/database/reflection.rb', line 53

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

#db_read_write?Boolean

Returns:

  • (Boolean)


63
64
65
# File 'lib/gitlab/database/reflection.rb', line 63

def db_read_write?
  !db_read_only?
end

#exists?Boolean

Returns:

  • (Boolean)


89
90
91
92
93
94
95
96
97
98
# File 'lib/gitlab/database/reflection.rb', line 89

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

#flavorObject



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
# File 'lib/gitlab/database/reflection.rb', line 108

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

#human_adapter_nameObject



40
41
42
43
44
45
46
# File 'lib/gitlab/database/reflection.rb', line 40

def human_adapter_name
  if postgresql?
    'PostgreSQL'
  else
    'Unknown'
  end
end

#postgresql?Boolean

Returns:

  • (Boolean)


48
49
50
# File 'lib/gitlab/database/reflection.rb', line 48

def postgresql?
  adapter_name.casecmp('postgresql') == 0
end

#postgresql_minimum_supported_version?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/gitlab/database/reflection.rb', line 75

def postgresql_minimum_supported_version?
  version.to_f >= MINIMUM_POSTGRES_VERSION
end

#system_idObject



100
101
102
103
104
105
106
# File 'lib/gitlab/database/reflection.rb', line 100

def system_id
  row = connection
    .execute('SELECT system_identifier FROM pg_control_system()')
    .first

  row['system_identifier']
end

#usernameObject



28
29
30
# File 'lib/gitlab/database/reflection.rb', line 28

def username
  config[:username] || ENV['USER']
end

#versionObject



67
68
69
# File 'lib/gitlab/database/reflection.rb', line 67

def version
  @version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end