diff options
Diffstat (limited to 'lib/gitlab/database/reflection.rb')
-rw-r--r-- | lib/gitlab/database/reflection.rb | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/lib/gitlab/database/reflection.rb b/lib/gitlab/database/reflection.rb new file mode 100644 index 00000000000..48a4de28541 --- /dev/null +++ b/lib/gitlab/database/reflection.rb @@ -0,0 +1,115 @@ +# 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 + + private + + def connection + model.connection + end + end + end +end |