Commit e10e31d5 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'yorick/refactor-database-connection-class' into 'master'

Refactor Database::Connection into separate types

See merge request gitlab-org/gitlab!72381
parents 5ecd9959 9bb48c9b
# frozen_string_literal: true # frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base class ApplicationRecord < ActiveRecord::Base
include DatabaseReflection
include Transactions
include LegacyBulkInsert
self.abstract_class = true self.abstract_class = true
alias_method :reset, :reload alias_method :reset, :reload
......
...@@ -244,11 +244,11 @@ module ApplicationSettingImplementation ...@@ -244,11 +244,11 @@ module ApplicationSettingImplementation
end end
def home_page_url_column_exists? def home_page_url_column_exists?
::Gitlab::Database.main.cached_column_exists?(:application_settings, :home_page_url) ApplicationSetting.database.cached_column_exists?(:home_page_url)
end end
def help_page_support_url_column_exists? def help_page_support_url_column_exists?
::Gitlab::Database.main.cached_column_exists?(:application_settings, :help_page_support_url) ApplicationSetting.database.cached_column_exists?(:help_page_support_url)
end end
def disabled_oauth_sign_in_sources=(sources) def disabled_oauth_sign_in_sources=(sources)
......
...@@ -127,7 +127,7 @@ module CascadingNamespaceSettingAttribute ...@@ -127,7 +127,7 @@ module CascadingNamespaceSettingAttribute
end end
def alias_boolean(attribute) def alias_boolean(attribute)
return unless Gitlab::Database.main.exists? && type_for_attribute(attribute).type == :boolean return unless database.exists? && type_for_attribute(attribute).type == :boolean
alias_method :"#{attribute}?", attribute alias_method :"#{attribute}?", attribute
end end
......
# frozen_string_literal: true
# A module that makes it easier/less verbose to reflect upon a database
# connection.
#
# Using this module you can write this:
#
# User.database.database_name
#
# Instead of this:
#
# Gitlab::Database::Reflection.new(User).database_name
module DatabaseReflection
extend ActiveSupport::Concern
class_methods do
def database
@database_reflection ||= ::Gitlab::Database::Reflection.new(self)
end
end
end
# frozen_string_literal: true
module LegacyBulkInsert
extend ActiveSupport::Concern
class_methods do
# Bulk inserts a number of rows into a table, optionally returning their
# IDs.
#
# This method is deprecated, and you should use the BulkInsertSafe module
# instead.
#
# table - The name of the table to insert the rows into.
# rows - An Array of Hash instances, each mapping the columns to their
# values.
# return_ids - When set to true the return value will be an Array of IDs of
# the inserted rows
# disable_quote - A key or an Array of keys to exclude from quoting (You
# become responsible for protection from SQL injection for
# these keys!)
# on_conflict - Defines an upsert. Values can be: :disabled (default) or
# :do_nothing
def legacy_bulk_insert(table, rows, return_ids: false, disable_quote: [], on_conflict: nil)
return if rows.empty?
keys = rows.first.keys
columns = keys.map { |key| connection.quote_column_name(key) }
disable_quote = Array(disable_quote).to_set
tuples = rows.map do |row|
keys.map do |k|
disable_quote.include?(k) ? row[k] : connection.quote(row[k])
end
end
sql = <<-EOF
INSERT INTO #{table} (#{columns.join(', ')})
VALUES #{tuples.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')}
EOF
sql = "#{sql} ON CONFLICT DO NOTHING" if on_conflict == :do_nothing
sql = "#{sql} RETURNING id" if return_ids
result = connection.execute(sql)
if return_ids
result.values.map { |tuple| tuple[0].to_i }
else
[]
end
end
end
end
...@@ -39,7 +39,7 @@ module Sha256Attribute ...@@ -39,7 +39,7 @@ module Sha256Attribute
end end
def database_exists? def database_exists?
Gitlab::Database.main.exists? database.exists?
end end
end end
end end
...@@ -32,7 +32,7 @@ module ShaAttribute ...@@ -32,7 +32,7 @@ module ShaAttribute
end end
def database_exists? def database_exists?
Gitlab::Database.main.exists? database.exists?
end end
end end
end end
......
# frozen_string_literal: true
module Transactions
extend ActiveSupport::Concern
class_methods do
# inside_transaction? will return true if the caller is running within a
# transaction. Handles special cases when running inside a test environment,
# where tests may be wrapped in transactions
def inside_transaction?
base = Rails.env.test? ? @open_transactions_baseline.to_i : 0
connection.open_transactions > base
end
# These methods that access @open_transactions_baseline are not thread-safe.
# These are fine though because we only call these in RSpec's main thread.
# If we decide to run specs multi-threaded, we would need to use something
# like ThreadGroup to keep track of this value
def set_open_transactions_baseline
@open_transactions_baseline = connection.open_transactions
end
def reset_open_transactions_baseline
@open_transactions_baseline = 0
end
end
end
...@@ -39,7 +39,7 @@ module X509SerialNumberAttribute ...@@ -39,7 +39,7 @@ module X509SerialNumberAttribute
end end
def database_exists? def database_exists?
Gitlab::Database.main.exists? database.exists?
end end
end end
end end
...@@ -300,7 +300,7 @@ class Deployment < ApplicationRecord ...@@ -300,7 +300,7 @@ class Deployment < ApplicationRecord
"#{id} as deployment_id", "#{id} as deployment_id",
"#{environment_id} as environment_id").to_sql "#{environment_id} as environment_id").to_sql
# We don't use `Gitlab::Database.main.bulk_insert` here so that we don't need to # We don't use `ApplicationRecord.legacy_bulk_insert` here so that we don't need to
# first pluck lots of IDs into memory. # first pluck lots of IDs into memory.
# #
# We also ignore any duplicates so this method can be called multiple times # We also ignore any duplicates so this method can be called multiple times
......
...@@ -88,7 +88,7 @@ module DesignManagement ...@@ -88,7 +88,7 @@ module DesignManagement
rows = design_actions.map { |action| action.row_attrs(version) } rows = design_actions.map { |action| action.row_attrs(version) }
Gitlab::Database.main.bulk_insert(::DesignManagement::Action.table_name, rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(::DesignManagement::Action.table_name, rows) # rubocop:disable Gitlab/BulkInsert
version.designs.reset version.designs.reset
version.validate! version.validate!
design_actions.each(&:performed) design_actions.each(&:performed)
......
...@@ -26,7 +26,7 @@ class MergeRequestContextCommit < ApplicationRecord ...@@ -26,7 +26,7 @@ class MergeRequestContextCommit < ApplicationRecord
# create MergeRequestContextCommit by given commit sha and it's diff file record # create MergeRequestContextCommit by given commit sha and it's diff file record
def self.bulk_insert(rows, **args) def self.bulk_insert(rows, **args)
Gitlab::Database.main.bulk_insert('merge_request_context_commits', rows, **args) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert('merge_request_context_commits', rows, **args) # rubocop:disable Gitlab/BulkInsert
end end
def to_commit def to_commit
......
...@@ -14,7 +14,7 @@ class MergeRequestContextCommitDiffFile < ApplicationRecord ...@@ -14,7 +14,7 @@ class MergeRequestContextCommitDiffFile < ApplicationRecord
# create MergeRequestContextCommitDiffFile by given diff file record(s) # create MergeRequestContextCommitDiffFile by given diff file record(s)
def self.bulk_insert(*args) def self.bulk_insert(*args)
Gitlab::Database.main.bulk_insert('merge_request_context_commit_diff_files', *args) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert('merge_request_context_commit_diff_files', *args) # rubocop:disable Gitlab/BulkInsert
end end
def path def path
......
...@@ -515,7 +515,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -515,7 +515,7 @@ class MergeRequestDiff < ApplicationRecord
transaction do transaction do
MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all
Gitlab::Database.main.bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
save! save!
end end
...@@ -535,7 +535,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -535,7 +535,7 @@ class MergeRequestDiff < ApplicationRecord
transaction do transaction do
MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all
Gitlab::Database.main.bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
update!(stored_externally: false) update!(stored_externally: false)
end end
...@@ -595,7 +595,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -595,7 +595,7 @@ class MergeRequestDiff < ApplicationRecord
rows = build_external_merge_request_diff_files(rows) if use_external_diff? rows = build_external_merge_request_diff_files(rows) if use_external_diff?
# Faster inserts # Faster inserts
Gitlab::Database.main.bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert('merge_request_diff_files', rows) # rubocop:disable Gitlab/BulkInsert
end end
def build_external_diff_tempfile(rows) def build_external_diff_tempfile(rows)
......
...@@ -74,7 +74,7 @@ class MergeRequestDiffCommit < ApplicationRecord ...@@ -74,7 +74,7 @@ class MergeRequestDiffCommit < ApplicationRecord
) )
end end
Gitlab::Database.main.bulk_insert(self.table_name, rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(self.table_name, rows) # rubocop:disable Gitlab/BulkInsert
end end
def self.prepare_commits_for_bulk_insert(commits) def self.prepare_commits_for_bulk_insert(commits)
......
...@@ -181,12 +181,12 @@ module DesignManagement ...@@ -181,12 +181,12 @@ module DesignManagement
) )
end end
# TODO Replace `Gitlab::Database.main.bulk_insert` with `BulkInsertSafe` # TODO Replace `ApplicationRecord.legacy_bulk_insert` with `BulkInsertSafe`
# once https://gitlab.com/gitlab-org/gitlab/-/issues/247718 is fixed. # once https://gitlab.com/gitlab-org/gitlab/-/issues/247718 is fixed.
# When this is fixed, we can remove the call to # When this is fixed, we can remove the call to
# `with_project_iid_supply` above, since the objects will be instantiated # `with_project_iid_supply` above, since the objects will be instantiated
# and callbacks (including `ensure_project_iid!`) will fire. # and callbacks (including `ensure_project_iid!`) will fire.
::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
DesignManagement::Design.table_name, DesignManagement::Design.table_name,
new_rows, new_rows,
return_ids: true return_ids: true
...@@ -207,9 +207,9 @@ module DesignManagement ...@@ -207,9 +207,9 @@ module DesignManagement
) )
end end
# TODO Replace `Gitlab::Database.main.bulk_insert` with `BulkInsertSafe` # TODO Replace `ApplicationRecord.legacy_bulk_insert` with `BulkInsertSafe`
# once https://gitlab.com/gitlab-org/gitlab/-/issues/247718 is fixed. # once https://gitlab.com/gitlab-org/gitlab/-/issues/247718 is fixed.
::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
DesignManagement::Version.table_name, DesignManagement::Version.table_name,
new_rows, new_rows,
return_ids: true return_ids: true
...@@ -239,7 +239,7 @@ module DesignManagement ...@@ -239,7 +239,7 @@ module DesignManagement
end end
# We cannot use `BulkInsertSafe` because of the uploader mounted in `Action`. # We cannot use `BulkInsertSafe` because of the uploader mounted in `Action`.
::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
DesignManagement::Action.table_name, DesignManagement::Action.table_name,
new_rows new_rows
) )
...@@ -278,7 +278,7 @@ module DesignManagement ...@@ -278,7 +278,7 @@ module DesignManagement
# We cannot use `BulkInsertSafe` due to the LfsObjectsProject#update_project_statistics # We cannot use `BulkInsertSafe` due to the LfsObjectsProject#update_project_statistics
# callback that fires after_commit. # callback that fires after_commit.
::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
LfsObjectsProject.table_name, LfsObjectsProject.table_name,
new_rows, new_rows,
on_conflict: :do_nothing # Upsert on_conflict: :do_nothing # Upsert
......
...@@ -99,7 +99,7 @@ module Issuable ...@@ -99,7 +99,7 @@ module Issuable
yield(event) yield(event)
end.compact end.compact
Gitlab::Database.main.bulk_insert(table_name, events) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(table_name, events) # rubocop:disable Gitlab/BulkInsert
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
module Packages module Packages
# rubocop: disable Gitlab/BulkInsert
class CreateDependencyService < BaseService class CreateDependencyService < BaseService
attr_reader :package, :dependencies attr_reader :package, :dependencies
...@@ -51,7 +52,7 @@ module Packages ...@@ -51,7 +52,7 @@ module Packages
} }
end end
ids = database.bulk_insert(Packages::Dependency.table_name, rows, return_ids: true, on_conflict: :do_nothing) ids = ApplicationRecord.legacy_bulk_insert(Packages::Dependency.table_name, rows, return_ids: true, on_conflict: :do_nothing)
return ids if ids.size == names_and_version_patterns.size return ids if ids.size == names_and_version_patterns.size
Packages::Dependency.uncached do Packages::Dependency.uncached do
...@@ -72,11 +73,8 @@ module Packages ...@@ -72,11 +73,8 @@ module Packages
} }
end end
database.bulk_insert(Packages::DependencyLink.table_name, rows) ApplicationRecord.legacy_bulk_insert(Packages::DependencyLink.table_name, rows)
end
def database
::Gitlab::Database.main
end end
end end
# rubocop: enable Gitlab/BulkInsert
end end
...@@ -41,7 +41,7 @@ module Packages ...@@ -41,7 +41,7 @@ module Packages
} }
end end
::Gitlab::Database.main.bulk_insert(::Packages::Nuget::DependencyLinkMetadatum.table_name, rows.compact) # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert(::Packages::Nuget::DependencyLinkMetadatum.table_name, rows.compact) # rubocop:disable Gitlab/BulkInsert
end end
def raw_dependency_for(dependency) def raw_dependency_for(dependency)
......
...@@ -15,7 +15,7 @@ module Packages ...@@ -15,7 +15,7 @@ module Packages
tags_to_create = @tags - existing_tags tags_to_create = @tags - existing_tags
@package.tags.with_name(tags_to_destroy).delete_all if tags_to_destroy.any? @package.tags.with_name(tags_to_destroy).delete_all if tags_to_destroy.any?
::Gitlab::Database.main.bulk_insert(Packages::Tag.table_name, rows(tags_to_create)) if tags_to_create.any? # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert(Packages::Tag.table_name, rows(tags_to_create)) if tags_to_create.any? # rubocop:disable Gitlab/BulkInsert
end end
private private
......
...@@ -21,7 +21,7 @@ module Projects ...@@ -21,7 +21,7 @@ module Projects
.update_all(share: update[:share]) .update_all(share: update[:share])
end end
Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
RepositoryLanguage.table_name, RepositoryLanguage.table_name,
detection.insertions(matching_programming_languages) detection.insertions(matching_programming_languages)
) )
......
...@@ -38,7 +38,7 @@ module Projects ...@@ -38,7 +38,7 @@ module Projects
rows = existent_lfs_objects rows = existent_lfs_objects
.not_linked_to_project(project) .not_linked_to_project(project)
.map { |existing_lfs_object| { project_id: project.id, lfs_object_id: existing_lfs_object.id } } .map { |existing_lfs_object| { project_id: project.id, lfs_object_id: existing_lfs_object.id } }
Gitlab::Database.main.bulk_insert(:lfs_objects_projects, rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(:lfs_objects_projects, rows) # rubocop:disable Gitlab/BulkInsert
iterations += 1 iterations += 1
linked_existing_objects += existent_lfs_objects.map(&:oid) linked_existing_objects += existent_lfs_objects.map(&:oid)
......
...@@ -23,7 +23,7 @@ module ResourceEvents ...@@ -23,7 +23,7 @@ module ResourceEvents
label_hash.merge(label_id: label.id, action: ResourceLabelEvent.actions['remove']) label_hash.merge(label_id: label.id, action: ResourceLabelEvent.actions['remove'])
end end
Gitlab::Database.main.bulk_insert(ResourceLabelEvent.table_name, labels) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(ResourceLabelEvent.table_name, labels) # rubocop:disable Gitlab/BulkInsert
resource.expire_note_etag_cache resource.expire_note_etag_cache
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user) if resource.is_a?(Issue) Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user) if resource.is_a?(Issue)
......
...@@ -25,7 +25,7 @@ module Suggestions ...@@ -25,7 +25,7 @@ module Suggestions
end end
rows.in_groups_of(100, false) do |rows| rows.in_groups_of(100, false) do |rows|
Gitlab::Database.main.bulk_insert('suggestions', rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert('suggestions', rows) # rubocop:disable Gitlab/BulkInsert
end end
Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_add_suggestion_action(note: @note) Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_add_suggestion_action(note: @note)
......
...@@ -153,9 +153,9 @@ ...@@ -153,9 +153,9 @@
%span.float-right %span.float-right
#{Rails::VERSION::STRING} #{Rails::VERSION::STRING}
%p %p
= Gitlab::Database.main.human_adapter_name = ApplicationRecord.database.human_adapter_name
%span.float-right %span.float-right
= Gitlab::Database.main.version = ApplicationRecord.database.version
%p %p
= _('Redis') = _('Redis')
%span.float-right %span.float-right
......
...@@ -54,7 +54,7 @@ module Gitlab ...@@ -54,7 +54,7 @@ module Gitlab
label_link_attrs << build_label_attrs(issue_id, import_label_id.to_i) label_link_attrs << build_label_attrs(issue_id, import_label_id.to_i)
Gitlab::Database.main.bulk_insert(LabelLink.table_name, label_link_attrs) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(LabelLink.table_name, label_link_attrs) # rubocop:disable Gitlab/BulkInsert
end end
def assign_issue(project_id, issue_id, assignee_ids) def assign_issue(project_id, issue_id, assignee_ids)
...@@ -62,7 +62,7 @@ module Gitlab ...@@ -62,7 +62,7 @@ module Gitlab
assignee_attrs = assignee_ids.map { |user_id| { issue_id: issue_id, user_id: user_id } } assignee_attrs = assignee_ids.map { |user_id| { issue_id: issue_id, user_id: user_id } }
Gitlab::Database.main.bulk_insert(IssueAssignee.table_name, assignee_attrs) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(IssueAssignee.table_name, assignee_attrs) # rubocop:disable Gitlab/BulkInsert
end end
def build_label_attrs(issue_id, label_id) def build_label_attrs(issue_id, label_id)
......
# frozen_string_literal: true # frozen_string_literal: true
raise "PostgreSQL is the only supported database from GitLab 12.1" unless raise "PostgreSQL is the only supported database from GitLab 12.1" unless
Gitlab::Database.main.postgresql? ApplicationRecord.database.postgresql?
Gitlab::Database.check_postgres_version_and_print_warning Gitlab::Database.check_postgres_version_and_print_warning
...@@ -14,7 +14,7 @@ end ...@@ -14,7 +14,7 @@ end
if defined?(ActiveRecord::Base) if defined?(ActiveRecord::Base)
Gitlab::Cluster::LifecycleEvents.on_before_fork do Gitlab::Cluster::LifecycleEvents.on_before_fork do
raise 'ActiveRecord connection not established. Unable to start.' unless Gitlab::Database.main.exists? raise 'ActiveRecord connection not established. Unable to start.' unless ApplicationRecord.database.exists?
# the following is highly recommended for Rails + "preload_app true" # the following is highly recommended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection # as there's no need for the master process to hold a connection
......
...@@ -10,8 +10,8 @@ if Gitlab::Runtime.console? ...@@ -10,8 +10,8 @@ if Gitlab::Runtime.console?
puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision}) #{Gitlab.ee? ? 'EE' : 'FOSS'}" puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision}) #{Gitlab.ee? ? 'EE' : 'FOSS'}"
puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.version)}" puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.version)}"
if Gitlab::Database.main.exists? if ApplicationRecord.database.exists?
puts " #{Gitlab::Database.main.human_adapter_name}:".ljust(justify) + Gitlab::Database.main.version puts " #{ApplicationRecord.database.human_adapter_name}:".ljust(justify) + ApplicationRecord.database.version
Gitlab.ee do Gitlab.ee do
if Gitlab::Geo.connected? && Gitlab::Geo.enabled? if Gitlab::Geo.connected? && Gitlab::Geo.enabled?
......
...@@ -20,7 +20,7 @@ module Sidekiq ...@@ -20,7 +20,7 @@ module Sidekiq
module NoEnqueueingFromTransactions module NoEnqueueingFromTransactions
%i(perform_async perform_at perform_in).each do |name| %i(perform_async perform_at perform_in).each do |name|
define_method(name) do |*args| define_method(name) do |*args|
if !Sidekiq::Worker.skip_transaction_check && Gitlab::Database.main.inside_transaction? if !Sidekiq::Worker.skip_transaction_check && ApplicationRecord.inside_transaction?
begin begin
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
`#{self}.#{name}` cannot be called inside a transaction as this can lead to `#{self}.#{name}` cannot be called inside a transaction as this can lead to
......
...@@ -20,7 +20,7 @@ class MigrateSamlIdentitiesToScimIdentities < ActiveRecord::Migration[6.0] ...@@ -20,7 +20,7 @@ class MigrateSamlIdentitiesToScimIdentities < ActiveRecord::Migration[6.0]
record.attributes.extract!("extern_uid", "user_id", "group_id", "active", "created_at", "updated_at") record.attributes.extract!("extern_uid", "user_id", "group_id", "active", "created_at", "updated_at")
end end
Gitlab::Database.main.bulk_insert(:scim_identities, data_to_insert, on_conflict: :do_nothing) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(:scim_identities, data_to_insert, on_conflict: :do_nothing) # rubocop:disable Gitlab/BulkInsert
end end
end end
......
...@@ -12,13 +12,13 @@ necessary to add database (version) specific behavior. ...@@ -12,13 +12,13 @@ necessary to add database (version) specific behavior.
To facilitate this we have the following methods that you can use: To facilitate this we have the following methods that you can use:
- `Gitlab::Database.main.version`: returns the PostgreSQL version number as a string - `ApplicationRecord.database.version`: returns the PostgreSQL version number as a string
in the format `X.Y.Z`. in the format `X.Y.Z`.
This allows you to write code such as: This allows you to write code such as:
```ruby ```ruby
if Gitlab::Database.main.version.to_f >= 11.7 if ApplicationRecord.database.version.to_f >= 11.7
run_really_fast_query run_really_fast_query
else else
run_fast_query run_fast_query
......
...@@ -421,19 +421,19 @@ module EE ...@@ -421,19 +421,19 @@ module EE
end end
def elasticsearch_indexing_column_exists? def elasticsearch_indexing_column_exists?
::Gitlab::Database.main.cached_column_exists?(:application_settings, :elasticsearch_indexing) self.class.database.cached_column_exists?(:elasticsearch_indexing)
end end
def elasticsearch_pause_indexing_column_exists? def elasticsearch_pause_indexing_column_exists?
::Gitlab::Database.main.cached_column_exists?(:application_settings, :elasticsearch_pause_indexing) self.class.database.cached_column_exists?(:elasticsearch_pause_indexing)
end end
def elasticsearch_search_column_exists? def elasticsearch_search_column_exists?
::Gitlab::Database.main.cached_column_exists?(:application_settings, :elasticsearch_search) self.class.database.cached_column_exists?(:elasticsearch_search)
end end
def email_additional_text_column_exists? def email_additional_text_column_exists?
::Gitlab::Database.main.cached_column_exists?(:application_settings, :email_additional_text) self.class.database.cached_column_exists?(:email_additional_text)
end end
def check_geo_node_allowed_ips def check_geo_node_allowed_ips
......
...@@ -37,7 +37,7 @@ class ElasticsearchIndexedNamespace < ApplicationRecord ...@@ -37,7 +37,7 @@ class ElasticsearchIndexedNamespace < ApplicationRecord
{ created_at: now, updated_at: now, namespace_id: id } { created_at: now, updated_at: now, namespace_id: id }
end end
Gitlab::Database.main.bulk_insert(table_name, insert_rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(table_name, insert_rows) # rubocop:disable Gitlab/BulkInsert
invalidate_elasticsearch_indexes_cache! invalidate_elasticsearch_indexes_cache!
jobs = batch_ids.map { |id| [id, :index] } jobs = batch_ids.map { |id| [id, :index] }
......
...@@ -18,7 +18,7 @@ module AuditEvents ...@@ -18,7 +18,7 @@ module AuditEvents
return if collection.empty? return if collection.empty?
collection.in_groups_of(BATCH_SIZE, false) do |services| collection.in_groups_of(BATCH_SIZE, false) do |services|
::Gitlab::Database.main.bulk_insert(::AuditEvent.table_name, services.map(&:attributes)) # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert(::AuditEvent.table_name, services.map(&:attributes)) # rubocop:disable Gitlab/BulkInsert
services.each(&:log_security_event_to_file) services.each(&:log_security_event_to_file)
end end
......
...@@ -15,7 +15,7 @@ module Iterations ...@@ -15,7 +15,7 @@ module Iterations
return ::ServiceResponse.error(message: _('Cadence is not automated'), http_status: 422) unless cadence.can_be_automated? return ::ServiceResponse.error(message: _('Cadence is not automated'), http_status: 422) unless cadence.can_be_automated?
update_existing_iterations! update_existing_iterations!
::Gitlab::Database.main.bulk_insert(Iteration.table_name, build_new_iterations) # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert(Iteration.table_name, build_new_iterations) # rubocop:disable Gitlab/BulkInsert
cadence.update!(last_run_date: compute_last_run_date) cadence.update!(last_run_date: compute_last_run_date)
......
...@@ -20,8 +20,8 @@ module Iterations ...@@ -20,8 +20,8 @@ module Iterations
ApplicationRecord.transaction do ApplicationRecord.transaction do
issues.update_all(sprint_id: to_iteration.id, updated_at: rolled_over_at) issues.update_all(sprint_id: to_iteration.id, updated_at: rolled_over_at)
Gitlab::Database.main.bulk_insert(ResourceIterationEvent.table_name, remove_iteration_events) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(ResourceIterationEvent.table_name, remove_iteration_events) # rubocop:disable Gitlab/BulkInsert
Gitlab::Database.main.bulk_insert(ResourceIterationEvent.table_name, add_iteration_events) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(ResourceIterationEvent.table_name, add_iteration_events) # rubocop:disable Gitlab/BulkInsert
end end
end end
......
...@@ -10,7 +10,7 @@ module ResourceEvents ...@@ -10,7 +10,7 @@ module ResourceEvents
end end
def execute def execute
::Gitlab::Database.main.bulk_insert(ResourceWeightEvent.table_name, resource_weight_changes) # rubocop:disable Gitlab/BulkInsert ::ApplicationRecord.legacy_bulk_insert(ResourceWeightEvent.table_name, resource_weight_changes) # rubocop:disable Gitlab/BulkInsert
resource.expire_note_etag_cache resource.expire_note_etag_cache
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_weight_changed_action(author: user) Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_weight_changed_action(author: user)
......
...@@ -163,7 +163,7 @@ module Geo ...@@ -163,7 +163,7 @@ module Geo
def update_pending_resources def update_pending_resources
if reload_queue? if reload_queue?
@pending_resources = Gitlab::Database.main.geo_uncached_queries { load_pending_resources } @pending_resources = Gitlab::Geo.uncached_queries { load_pending_resources }
set_backoff_time! if should_apply_backoff? set_backoff_time! if should_apply_backoff?
end end
end end
......
...@@ -49,7 +49,7 @@ module EE ...@@ -49,7 +49,7 @@ module EE
} }
end end
Gitlab::Database.main.bulk_insert(:gitlab_subscriptions, rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(:gitlab_subscriptions, rows) # rubocop:disable Gitlab/BulkInsert
end end
end end
end end
......
# frozen_string_literal: true
module EE
module Gitlab
module Database
module Connection
extend ActiveSupport::Concern
def geo_uncached_queries(&block)
raise 'No block given' unless block_given?
scope.uncached do
if ::Gitlab::Geo.secondary?
Geo::TrackingBase.uncached(&block)
else
yield
end
end
end
end
end
end
end
...@@ -14,7 +14,7 @@ module EE ...@@ -14,7 +14,7 @@ module EE
# This method can be called/loaded before the database # This method can be called/loaded before the database
# has been created. With this guard clause we prevent querying # has been created. With this guard clause we prevent querying
# the License table until the table exists # the License table until the table exists
return [] unless ::Gitlab::Database.main.cached_table_exists?('licenses') && return [] unless License.database.cached_table_exists? &&
License.feature_available?(:custom_project_templates) License.feature_available?(:custom_project_templates)
[::Gitlab::ImportSources::ImportSource.new('gitlab_custom_project_template', [::Gitlab::ImportSources::ImportSource.new('gitlab_custom_project_template',
......
...@@ -83,7 +83,7 @@ module EE ...@@ -83,7 +83,7 @@ module EE
action = route_hash[:action] action = route_hash[:action]
if ALLOWLISTED_GEO_ROUTES[controller]&.include?(action) if ALLOWLISTED_GEO_ROUTES[controller]&.include?(action)
::Gitlab::Database.main.db_read_write? ::ApplicationRecord.database.db_read_write?
else else
ALLOWLISTED_GEO_ROUTES_TRACKING_DB[controller]&.include?(action) ALLOWLISTED_GEO_ROUTES_TRACKING_DB[controller]&.include?(action)
end end
......
...@@ -213,5 +213,17 @@ module Gitlab ...@@ -213,5 +213,17 @@ module Gitlab
[1, capacity].max # at least 1 [1, capacity].max # at least 1
end end
def self.uncached_queries(&block)
raise 'No block given' unless block_given?
ApplicationRecord.uncached do
if ::Gitlab::Geo.secondary?
::Geo::TrackingBase.uncached(&block)
else
yield
end
end
end
end end
end end
...@@ -9,7 +9,7 @@ module Gitlab ...@@ -9,7 +9,7 @@ module Gitlab
return '' unless Gitlab::Geo.secondary? return '' unless Gitlab::Geo.secondary?
return 'Geo database configuration file is missing.' unless Gitlab::Geo.geo_database_configured? return 'Geo database configuration file is missing.' unless Gitlab::Geo.geo_database_configured?
return 'An existing tracking database cannot be reused.' if reusing_existing_tracking_database? return 'An existing tracking database cannot be reused.' if reusing_existing_tracking_database?
return 'Geo node has a database that is writable which is an indication it is not configured for replication with the primary node.' unless Gitlab::Database.main.db_read_only? return 'Geo node has a database that is writable which is an indication it is not configured for replication with the primary node.' unless ApplicationRecord.database.db_read_only?
return 'Geo node does not appear to be replicating the database from the primary node.' if replication_enabled? && !replication_working? return 'Geo node does not appear to be replicating the database from the primary node.' if replication_enabled? && !replication_working?
return "Geo database version (#{database_version}) does not match latest migration (#{migration_version}).\nYou may have to run `gitlab-rake geo:db:migrate` as root on the secondary." unless database_migration_version_match? return "Geo database version (#{database_version}) does not match latest migration (#{migration_version}).\nYou may have to run `gitlab-rake geo:db:migrate` as root on the secondary." unless database_migration_version_match?
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::Connection do
include ::EE::GeoHelpers
let(:connection) { described_class.new }
describe '#geo_uncached_queries' do
context 'when no block is given' do
it 'raises error' do
expect do
connection.geo_uncached_queries
end.to raise_error('No block given')
end
end
context 'when the current node is a primary' do
let!(:primary) { create(:geo_node, :primary) }
it 'wraps the block in an ActiveRecord::Base.uncached block' do
stub_current_geo_node(primary)
expect(Geo::TrackingBase).not_to receive(:uncached)
expect(ActiveRecord::Base).to receive(:uncached).and_call_original
expect do |b|
connection.geo_uncached_queries(&b)
end.to yield_control
end
end
context 'when the current node is a secondary' do
let!(:primary) { create(:geo_node, :primary) }
let!(:secondary) { create(:geo_node) }
it 'wraps the block in a Geo::TrackingBase.uncached block and an ActiveRecord::Base.uncached block' do
stub_current_geo_node(secondary)
expect(Geo::TrackingBase).to receive(:uncached).and_call_original
expect(ActiveRecord::Base).to receive(:uncached).and_call_original
expect do |b|
connection.geo_uncached_queries(&b)
end.to yield_control
end
end
context 'when there is no current node' do
it 'wraps the block in an ActiveRecord::Base.uncached block' do
expect(Geo::TrackingBase).not_to receive(:uncached)
expect(ActiveRecord::Base).to receive(:uncached).and_call_original
expect do |b|
connection.geo_uncached_queries(&b)
end.to yield_control
end
end
end
end
...@@ -37,7 +37,7 @@ RSpec.describe Gitlab::Geo::HealthCheck, :geo do ...@@ -37,7 +37,7 @@ RSpec.describe Gitlab::Geo::HealthCheck, :geo do
before do before do
allow(Gitlab::Geo).to receive(:secondary?) { true } allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Geo).to receive(:geo_database_configured?) { geo_database_configured } allow(Gitlab::Geo).to receive(:geo_database_configured?) { geo_database_configured }
allow(Gitlab::Database.main).to receive(:db_read_only?) { db_read_only } allow(ApplicationRecord.database).to receive(:db_read_only?) { db_read_only }
end end
context 'when the Geo tracking DB is not configured' do context 'when the Geo tracking DB is not configured' do
...@@ -124,8 +124,8 @@ RSpec.describe Gitlab::Geo::HealthCheck, :geo do ...@@ -124,8 +124,8 @@ RSpec.describe Gitlab::Geo::HealthCheck, :geo do
describe '#db_replication_lag_seconds' do describe '#db_replication_lag_seconds' do
before do before do
query = 'SELECT CASE WHEN pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn() THEN 0 ELSE EXTRACT (EPOCH FROM now() - pg_last_xact_replay_timestamp())::INTEGER END AS replication_lag' query = 'SELECT CASE WHEN pg_last_wal_receive_lsn() = pg_last_wal_replay_lsn() THEN 0 ELSE EXTRACT (EPOCH FROM now() - pg_last_xact_replay_timestamp())::INTEGER END AS replication_lag'
allow(Gitlab::Database.main).to receive(:pg_last_wal_receive_lsn).and_return('pg_last_wal_receive_lsn') allow(ApplicationRecord.database).to receive(:pg_last_wal_receive_lsn).and_return('pg_last_wal_receive_lsn')
allow(Gitlab::Database.main).to receive(:pg_last_wal_replay_lsn).and_return('pg_last_wal_replay_lsn') allow(ApplicationRecord.database).to receive(:pg_last_wal_replay_lsn).and_return('pg_last_wal_replay_lsn')
allow(ActiveRecord::Base).to receive_message_chain('connection.execute').with(query).and_return([{ 'replication_lag' => lag_in_seconds }]) allow(ActiveRecord::Base).to receive_message_chain('connection.execute').with(query).and_return([{ 'replication_lag' => lag_in_seconds }])
end end
......
...@@ -426,4 +426,51 @@ RSpec.describe Gitlab::Geo, :geo, :request_store do ...@@ -426,4 +426,51 @@ RSpec.describe Gitlab::Geo, :geo, :request_store do
end end
end end
end end
describe '.uncached_queries' do
context 'when no block is given' do
it 'raises error' do
expect do
described_class.uncached_queries
end.to raise_error('No block given')
end
end
context 'when the current node is a primary' do
it 'wraps the block in an ApplicationRecord.uncached block' do
stub_current_geo_node(primary_node)
expect(Geo::TrackingBase).not_to receive(:uncached)
expect(ApplicationRecord).to receive(:uncached).and_call_original
expect do |b|
described_class.uncached_queries(&b)
end.to yield_control
end
end
context 'when the current node is a secondary' do
it 'wraps the block in a Geo::TrackingBase.uncached block and an ApplicationRecord.uncached block' do
stub_current_geo_node(secondary_node)
expect(Geo::TrackingBase).to receive(:uncached).and_call_original
expect(ApplicationRecord).to receive(:uncached).and_call_original
expect do |b|
described_class.uncached_queries(&b)
end.to yield_control
end
end
context 'when there is no current node' do
it 'wraps the block in an ApplicationRecord.uncached block' do
expect(Geo::TrackingBase).not_to receive(:uncached)
expect(ApplicationRecord).to receive(:uncached).and_call_original
expect do |b|
described_class.uncached_queries(&b)
end.to yield_control
end
end
end
end end
...@@ -15,7 +15,7 @@ module AfterCommitQueue ...@@ -15,7 +15,7 @@ module AfterCommitQueue
end end
def run_after_commit_or_now(&block) def run_after_commit_or_now(&block)
if Gitlab::Database.main.inside_transaction? if ApplicationRecord.inside_transaction?
if ActiveRecord::Base.connection.current_transaction.records&.include?(self) if ActiveRecord::Base.connection.current_transaction.records&.include?(self)
run_after_commit(&block) run_after_commit(&block)
else else
......
...@@ -6,6 +6,8 @@ require 'flipper/adapters/active_support_cache_store' ...@@ -6,6 +6,8 @@ require 'flipper/adapters/active_support_cache_store'
class Feature class Feature
# Classes to override flipper table names # Classes to override flipper table names
class FlipperFeature < Flipper::Adapters::ActiveRecord::Feature class FlipperFeature < Flipper::Adapters::ActiveRecord::Feature
include DatabaseReflection
# Using `self.table_name` won't work. ActiveRecord bug? # Using `self.table_name` won't work. ActiveRecord bug?
superclass.table_name = 'features' superclass.table_name = 'features'
...@@ -36,7 +38,7 @@ class Feature ...@@ -36,7 +38,7 @@ class Feature
end end
def persisted_names def persisted_names
return [] unless Gitlab::Database.main.exists? return [] unless ApplicationRecord.database.exists?
# This loads names of all stored feature flags # This loads names of all stored feature flags
# and returns a stable Set in the following order: # and returns a stable Set in the following order:
...@@ -73,7 +75,7 @@ class Feature ...@@ -73,7 +75,7 @@ class Feature
# During setup the database does not exist yet. So we haven't stored a value # During setup the database does not exist yet. So we haven't stored a value
# for the feature yet and return the default. # for the feature yet and return the default.
return default_enabled unless Gitlab::Database.main.exists? return default_enabled unless ApplicationRecord.database.exists?
feature = get(key) feature = get(key)
......
...@@ -15,7 +15,7 @@ class Feature ...@@ -15,7 +15,7 @@ class Feature
def server_feature_flags(project = nil) def server_feature_flags(project = nil)
# We need to check that both the DB connection and table exists # We need to check that both the DB connection and table exists
return {} unless ::Gitlab::Database.main.cached_table_exists?(FlipperFeature.table_name) return {} unless FlipperFeature.database.cached_table_exists?
Feature.persisted_names Feature.persisted_names
.select { |f| f.start_with?(PREFIX) } .select { |f| f.start_with?(PREFIX) }
......
...@@ -189,7 +189,7 @@ module Gitlab ...@@ -189,7 +189,7 @@ module Gitlab
end end
def perform(start_id, stop_id) def perform(start_id, stop_id)
Gitlab::Database.main.bulk_insert(:project_repositories, project_repositories(start_id, stop_id)) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(:project_repositories, project_repositories(start_id, stop_id)) # rubocop:disable Gitlab/BulkInsert
end end
private private
......
...@@ -120,7 +120,10 @@ module Gitlab ...@@ -120,7 +120,10 @@ module Gitlab
end end
def connection def connection
@connection ||= Gitlab::Database.databases.fetch(database, Gitlab::Database.main).scope.connection @connection ||= Gitlab::Database
.database_base_models
.fetch(database, Gitlab::Database::PRIMARY_DATABASE_NAME)
.connection
end end
end end
end end
......
...@@ -34,7 +34,7 @@ module Gitlab ...@@ -34,7 +34,7 @@ module Gitlab
end end
end end
Gitlab::Database.main.bulk_insert(TEMP_TABLE, fingerprints) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(TEMP_TABLE, fingerprints) # rubocop:disable Gitlab/BulkInsert
execute("ANALYZE #{TEMP_TABLE}") execute("ANALYZE #{TEMP_TABLE}")
......
...@@ -65,7 +65,7 @@ module Gitlab ...@@ -65,7 +65,7 @@ module Gitlab
next if service_ids.empty? next if service_ids.empty?
migrated_ids += service_ids migrated_ids += service_ids
Gitlab::Database.main.bulk_insert(table, data) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(table, data) # rubocop:disable Gitlab/BulkInsert
end end
return if migrated_ids.empty? return if migrated_ids.empty?
......
...@@ -21,7 +21,7 @@ module Gitlab ...@@ -21,7 +21,7 @@ module Gitlab
} }
end end
Gitlab::Database.main.bulk_insert(:issue_email_participants, rows, on_conflict: :do_nothing) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(:issue_email_participants, rows, on_conflict: :do_nothing) # rubocop:disable Gitlab/BulkInsert
end end
end end
end end
......
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
extend self extend self
def check def check
return [] if Gitlab::Database.main.postgresql_minimum_supported_version? return [] if ApplicationRecord.database.postgresql_minimum_supported_version?
[ [
{ {
...@@ -15,7 +15,7 @@ module Gitlab ...@@ -15,7 +15,7 @@ module Gitlab
'%{pg_version_minimum} is required for this version of GitLab. ' \ '%{pg_version_minimum} is required for this version of GitLab. ' \
'Please upgrade your environment to a supported PostgreSQL version, ' \ 'Please upgrade your environment to a supported PostgreSQL version, ' \
'see %{pg_requirements_url} for details.') % { 'see %{pg_requirements_url} for details.') % {
pg_version_current: Gitlab::Database.main.version, pg_version_current: ApplicationRecord.database.version,
pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION, pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
pg_requirements_url: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>' pg_requirements_url: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>'
} }
......
...@@ -85,7 +85,7 @@ module Gitlab ...@@ -85,7 +85,7 @@ module Gitlab
active_db_connection = ActiveRecord::Base.connection.active? rescue false active_db_connection = ActiveRecord::Base.connection.active? rescue false
active_db_connection && active_db_connection &&
Gitlab::Database.main.cached_table_exists?('application_settings') ApplicationSetting.database.cached_table_exists?
rescue ActiveRecord::NoDatabaseError rescue ActiveRecord::NoDatabaseError
false false
end end
......
...@@ -60,18 +60,7 @@ module Gitlab ...@@ -60,18 +60,7 @@ module Gitlab
# inherit from ApplicationRecord. # inherit from ApplicationRecord.
main: ::ActiveRecord::Base, main: ::ActiveRecord::Base,
ci: ::Ci::ApplicationRecord.connection_class? ? ::Ci::ApplicationRecord : nil ci: ::Ci::ApplicationRecord.connection_class? ? ::Ci::ApplicationRecord : nil
}.compact.freeze }.compact.with_indifferent_access.freeze
end
def self.databases
@databases ||= database_base_models
.transform_values { |connection_class| Connection.new(connection_class) }
.with_indifferent_access
.freeze
end
def self.main
databases[PRIMARY_DATABASE_NAME]
end end
# We configure the database connection pool size automatically based on the # We configure the database connection pool size automatically based on the
...@@ -110,8 +99,10 @@ module Gitlab ...@@ -110,8 +99,10 @@ module Gitlab
def self.check_postgres_version_and_print_warning def self.check_postgres_version_and_print_warning
return if Gitlab::Runtime.rails_runner? return if Gitlab::Runtime.rails_runner?
databases.each do |name, connection| database_base_models.each do |name, model|
next if connection.postgresql_minimum_supported_version? database = Gitlab::Database::Reflection.new(model)
next if database.postgresql_minimum_supported_version?
Kernel.warn ERB.new(Rainbow.new.wrap(<<~EOS).red).result Kernel.warn ERB.new(Rainbow.new.wrap(<<~EOS).red).result
...@@ -122,7 +113,7 @@ module Gitlab ...@@ -122,7 +113,7 @@ module Gitlab
 ███ ███  ██  ██ ██  ██ ██   ████ ██ ██   ████  ██████    ███ ███  ██  ██ ██  ██ ██   ████ ██ ██   ████  ██████  
****************************************************************************** ******************************************************************************
You are using PostgreSQL #{connection.version} for the #{name} database, but PostgreSQL >= <%= Gitlab::Database::MINIMUM_POSTGRES_VERSION %> You are using PostgreSQL #{database.version} for the #{name} database, but PostgreSQL >= <%= Gitlab::Database::MINIMUM_POSTGRES_VERSION %>
is required for this version of GitLab. is required for this version of GitLab.
<% if Rails.env.development? || Rails.env.test? %> <% if Rails.env.development? || Rails.env.test? %>
If using gitlab-development-kit, please find the relevant steps here: If using gitlab-development-kit, please find the relevant steps here:
......
...@@ -19,7 +19,7 @@ module Gitlab ...@@ -19,7 +19,7 @@ module Gitlab
# Note: to be deleted after the minimum PG version is set to 12.0 # Note: to be deleted after the minimum PG version is set to 12.0
def self.materialized_supported? def self.materialized_supported?
strong_memoize(:materialized_supported) do strong_memoize(:materialized_supported) do
Gitlab::Database.main.version.match?(/^1[2-9]\./) # version 12.x and above ApplicationRecord.database.version.match?(/^1[2-9]\./) # version 12.x and above
end end
end end
......
...@@ -5,8 +5,8 @@ module Gitlab ...@@ -5,8 +5,8 @@ module Gitlab
module EachDatabase module EachDatabase
class << self class << self
def each_database_connection def each_database_connection
Gitlab::Database.databases.each_pair do |connection_name, connection_wrapper| Gitlab::Database.database_base_models.each_pair do |connection_name, model|
connection = connection_wrapper.scope.connection connection = model.connection
with_shared_connection(connection, connection_name) do with_shared_connection(connection, connection_name) do
yield connection, connection_name yield connection, connection_name
......
...@@ -263,6 +263,21 @@ module Gitlab ...@@ -263,6 +263,21 @@ module Gitlab
) || raise(::ActiveRecord::ConnectionNotEstablished) ) || raise(::ActiveRecord::ConnectionNotEstablished)
end end
def wal_diff(location1, location2)
read_write do |connection|
lsn1 = connection.quote(location1)
lsn2 = connection.quote(location2)
query = <<-SQL.squish
SELECT pg_wal_lsn_diff(#{lsn1}, #{lsn2})
AS result
SQL
row = connection.select_all(query).first
row['result'] if row
end
end
private private
def ensure_caching! def ensure_caching!
......
...@@ -1260,8 +1260,8 @@ module Gitlab ...@@ -1260,8 +1260,8 @@ module Gitlab
def check_trigger_permissions!(table) def check_trigger_permissions!(table)
unless Grant.create_and_execute_trigger?(table) unless Grant.create_and_execute_trigger?(table)
dbname = Database.main.database_name dbname = ApplicationRecord.database.database_name
user = Database.main.username user = ApplicationRecord.database.username
raise <<-EOF raise <<-EOF
Your database user is not allowed to create, drop, or execute triggers on the Your database user is not allowed to create, drop, or execute triggers on the
...@@ -1583,8 +1583,8 @@ into similar problems in the future (e.g. when new tables are created). ...@@ -1583,8 +1583,8 @@ into similar problems in the future (e.g. when new tables are created).
def create_extension(extension) def create_extension(extension)
execute('CREATE EXTENSION IF NOT EXISTS %s' % extension) execute('CREATE EXTENSION IF NOT EXISTS %s' % extension)
rescue ActiveRecord::StatementInvalid => e rescue ActiveRecord::StatementInvalid => e
dbname = Database.main.database_name dbname = ApplicationRecord.database.database_name
user = Database.main.username user = ApplicationRecord.database.username
warn(<<~MSG) if e.to_s =~ /permission denied/ warn(<<~MSG) if e.to_s =~ /permission denied/
GitLab requires the PostgreSQL extension '#{extension}' installed in database '#{dbname}', but GitLab requires the PostgreSQL extension '#{extension}' installed in database '#{dbname}', but
...@@ -1611,8 +1611,8 @@ into similar problems in the future (e.g. when new tables are created). ...@@ -1611,8 +1611,8 @@ into similar problems in the future (e.g. when new tables are created).
def drop_extension(extension) def drop_extension(extension)
execute('DROP EXTENSION IF EXISTS %s' % extension) execute('DROP EXTENSION IF EXISTS %s' % extension)
rescue ActiveRecord::StatementInvalid => e rescue ActiveRecord::StatementInvalid => e
dbname = Database.main.database_name dbname = ApplicationRecord.database.database_name
user = Database.main.username user = ApplicationRecord.database.username
warn(<<~MSG) if e.to_s =~ /permission denied/ warn(<<~MSG) if e.to_s =~ /permission denied/
This migration attempts to drop the PostgreSQL extension '#{extension}' This migration attempts to drop the PostgreSQL extension '#{extension}'
......
...@@ -2,20 +2,14 @@ ...@@ -2,20 +2,14 @@
module Gitlab module Gitlab
module Database module Database
# Configuration settings and methods for interacting with a PostgreSQL # A class for reflecting upon a database and its settings, such as the
# database, with support for multiple databases. # adapter name, PostgreSQL version, and the presence of tables or columns.
class Connection class Reflection
attr_reader :scope attr_reader :model
# Initializes a new `Database`. def initialize(model)
# @model = model
# The `scope` argument must be an object (such as `ActiveRecord::Base`)
# that supports retrieving connections and connection pools.
def initialize(scope = ActiveRecord::Base)
@config = nil
@scope = scope
@version = nil @version = nil
@open_transactions_baseline = 0
end end
def config def config
...@@ -28,11 +22,7 @@ module Gitlab ...@@ -28,11 +22,7 @@ module Gitlab
# #
# - https://gitlab.com/gitlab-org/release/retrospectives/-/issues/39 # - https://gitlab.com/gitlab-org/release/retrospectives/-/issues/39
# - https://gitlab.com/gitlab-com/gl-infra/production/-/issues/5238 # - https://gitlab.com/gitlab-com/gl-infra/production/-/issues/5238
scope.connection_db_config.configuration_hash.with_indifferent_access model.connection_db_config.configuration_hash.with_indifferent_access
end
def pool_size
config[:pool] || Database.default_pool_size
end end
def username def username
...@@ -62,8 +52,7 @@ module Gitlab ...@@ -62,8 +52,7 @@ module Gitlab
# Check whether the underlying database is in read-only mode # Check whether the underlying database is in read-only mode
def db_read_only? def db_read_only?
pg_is_in_recovery = pg_is_in_recovery =
scope connection
.connection
.execute('SELECT pg_is_in_recovery()') .execute('SELECT pg_is_in_recovery()')
.first .first
.fetch('pg_is_in_recovery') .fetch('pg_is_in_recovery')
...@@ -87,58 +76,14 @@ module Gitlab ...@@ -87,58 +76,14 @@ module Gitlab
version.to_f >= MINIMUM_POSTGRES_VERSION version.to_f >= MINIMUM_POSTGRES_VERSION
end end
# Bulk inserts a number of rows into a table, optionally returning their def cached_column_exists?(column_name)
# IDs.
#
# table - The name of the table to insert the rows into.
# rows - An Array of Hash instances, each mapping the columns to their
# values.
# return_ids - When set to true the return value will be an Array of IDs of
# the inserted rows
# disable_quote - A key or an Array of keys to exclude from quoting (You
# become responsible for protection from SQL injection for
# these keys!)
# on_conflict - Defines an upsert. Values can be: :disabled (default) or
# :do_nothing
def bulk_insert(table, rows, return_ids: false, disable_quote: [], on_conflict: nil)
return if rows.empty?
keys = rows.first.keys
columns = keys.map { |key| connection.quote_column_name(key) }
disable_quote = Array(disable_quote).to_set
tuples = rows.map do |row|
keys.map do |k|
disable_quote.include?(k) ? row[k] : connection.quote(row[k])
end
end
sql = <<-EOF
INSERT INTO #{table} (#{columns.join(', ')})
VALUES #{tuples.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')}
EOF
sql = "#{sql} ON CONFLICT DO NOTHING" if on_conflict == :do_nothing
sql = "#{sql} RETURNING id" if return_ids
result = connection.execute(sql)
if return_ids
result.values.map { |tuple| tuple[0].to_i }
else
[]
end
end
def cached_column_exists?(table_name, column_name)
connection connection
.schema_cache.columns_hash(table_name) .schema_cache.columns_hash(model.table_name)
.has_key?(column_name.to_s) .has_key?(column_name.to_s)
end end
def cached_table_exists?(table_name) def cached_table_exists?
exists? && connection.schema_cache.data_source_exists?(table_name) exists? && connection.schema_cache.data_source_exists?(model.table_name)
end end
def exists? def exists?
...@@ -160,47 +105,11 @@ module Gitlab ...@@ -160,47 +105,11 @@ module Gitlab
row['system_identifier'] row['system_identifier']
end end
def pg_wal_lsn_diff(location1, location2)
lsn1 = connection.quote(location1)
lsn2 = connection.quote(location2)
query = <<-SQL.squish
SELECT pg_wal_lsn_diff(#{lsn1}, #{lsn2})
AS result
SQL
row = connection.select_all(query).first
row['result'] if row
end
# inside_transaction? will return true if the caller is running within a
# transaction. Handles special cases when running inside a test
# environment, where tests may be wrapped in transactions
def inside_transaction?
base = Rails.env.test? ? @open_transactions_baseline : 0
scope.connection.open_transactions > base
end
# These methods that access @open_transactions_baseline are not
# thread-safe. These are fine though because we only call these in
# RSpec's main thread. If we decide to run specs multi-threaded, we would
# need to use something like ThreadGroup to keep track of this value
def set_open_transactions_baseline
@open_transactions_baseline = scope.connection.open_transactions
end
def reset_open_transactions_baseline
@open_transactions_baseline = 0
end
private private
def connection def connection
scope.connection model.connection
end end
end end
end end
end end
Gitlab::Database::Connection.prepend_mod_with('Gitlab::Database::Connection')
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
# Bulk inserts the given rows into the database. # Bulk inserts the given rows into the database.
def bulk_insert(model, rows, batch_size: 100) def bulk_insert(model, rows, batch_size: 100)
rows.each_slice(batch_size) do |slice| rows.each_slice(batch_size) do |slice|
Gitlab::Database.main.bulk_insert(model.table_name, slice) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(model.table_name, slice) # rubocop:disable Gitlab/BulkInsert
log_and_increment_counter(slice.size, :imported) log_and_increment_counter(slice.size, :imported)
end end
......
...@@ -70,7 +70,7 @@ module Gitlab ...@@ -70,7 +70,7 @@ module Gitlab
# To work around this we're using bulk_insert with a single row. This # To work around this we're using bulk_insert with a single row. This
# allows us to efficiently insert data (even if it's just 1 row) # allows us to efficiently insert data (even if it's just 1 row)
# without having to use all sorts of hacks to disable callbacks. # without having to use all sorts of hacks to disable callbacks.
Gitlab::Database.main.bulk_insert(LegacyDiffNote.table_name, [{ ApplicationRecord.legacy_bulk_insert(LegacyDiffNote.table_name, [{
noteable_type: note.noteable_type, noteable_type: note.noteable_type,
system: false, system: false,
type: 'LegacyDiffNote', type: 'LegacyDiffNote',
......
...@@ -75,7 +75,7 @@ module Gitlab ...@@ -75,7 +75,7 @@ module Gitlab
end end
end end
Gitlab::Database.main.bulk_insert(IssueAssignee.table_name, assignees) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(IssueAssignee.table_name, assignees) # rubocop:disable Gitlab/BulkInsert
end end
end end
end end
......
...@@ -40,7 +40,7 @@ module Gitlab ...@@ -40,7 +40,7 @@ module Gitlab
} }
end end
Gitlab::Database.main.bulk_insert(LabelLink.table_name, rows) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(LabelLink.table_name, rows) # rubocop:disable Gitlab/BulkInsert
end end
def find_target_id def find_target_id
......
...@@ -37,7 +37,7 @@ module Gitlab ...@@ -37,7 +37,7 @@ module Gitlab
# We're using bulk_insert here so we can bypass any validations and # We're using bulk_insert here so we can bypass any validations and
# callbacks. Running these would result in a lot of unnecessary SQL # callbacks. Running these would result in a lot of unnecessary SQL
# queries being executed when importing large projects. # queries being executed when importing large projects.
Gitlab::Database.main.bulk_insert(Note.table_name, [attributes]) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(Note.table_name, [attributes]) # rubocop:disable Gitlab/BulkInsert
rescue ActiveRecord::InvalidForeignKey rescue ActiveRecord::InvalidForeignKey
# It's possible the project and the issue have been deleted since # It's possible the project and the issue have been deleted since
# scheduling this job. In this case we'll just skip creating the note. # scheduling this job. In this case we'll just skip creating the note.
......
...@@ -11,8 +11,8 @@ module Gitlab ...@@ -11,8 +11,8 @@ module Gitlab
# We use bulk_insert here so we can bypass any queries executed by # We use bulk_insert here so we can bypass any queries executed by
# callbacks or validation rules, as doing this wouldn't scale when # callbacks or validation rules, as doing this wouldn't scale when
# importing very large projects. # importing very large projects.
result = Gitlab::Database.main # rubocop:disable Gitlab/BulkInsert result = ApplicationRecord # rubocop:disable Gitlab/BulkInsert
.bulk_insert(relation.table_name, [attributes], return_ids: true) .legacy_bulk_insert(relation.table_name, [attributes], return_ids: true)
result.first result.first
end end
......
...@@ -18,7 +18,7 @@ module Gitlab ...@@ -18,7 +18,7 @@ module Gitlab
end end
# Newly detected languages, returned in a structure accepted by # Newly detected languages, returned in a structure accepted by
# Gitlab::Database.main.bulk_insert # ApplicationRecord.legacy_bulk_insert
def insertions(programming_languages) def insertions(programming_languages)
lang_to_id = programming_languages.to_h { |p| [p.name, p.id] } lang_to_id = programming_languages.to_h { |p| [p.name, p.id] }
......
...@@ -208,7 +208,12 @@ module Gitlab ...@@ -208,7 +208,12 @@ module Gitlab
end end
def pg_wal_lsn_diff(connection_name) def pg_wal_lsn_diff(connection_name)
Gitlab::Database.databases[connection_name].pg_wal_lsn_diff(job_wal_locations[connection_name], existing_wal_locations[connection_name]) model = Gitlab::Database.database_base_models[connection_name]
model.connection.load_balancer.wal_diff(
job_wal_locations[connection_name],
existing_wal_locations[connection_name]
)
end end
def strategy def strategy
......
...@@ -296,9 +296,11 @@ module Gitlab ...@@ -296,9 +296,11 @@ module Gitlab
version: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.container_registry_version } version: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.container_registry_version }
}, },
database: { database: {
adapter: alt_usage_data { Gitlab::Database.main.adapter_name }, # rubocop: disable UsageData/LargeTable
version: alt_usage_data { Gitlab::Database.main.version }, adapter: alt_usage_data { ApplicationRecord.database.adapter_name },
pg_system_id: alt_usage_data { Gitlab::Database.main.system_id } version: alt_usage_data { ApplicationRecord.database.version },
pg_system_id: alt_usage_data { ApplicationRecord.database.system_id }
# rubocop: enable UsageData/LargeTable
}, },
mail: { mail: {
smtp_server: alt_usage_data { ActionMailer::Base.smtp_settings[:address] } smtp_server: alt_usage_data { ActionMailer::Base.smtp_settings[:address] }
......
...@@ -182,9 +182,9 @@ namespace :gitlab do ...@@ -182,9 +182,9 @@ namespace :gitlab do
desc 'Enqueue an index for reindexing' desc 'Enqueue an index for reindexing'
task :enqueue_reindexing_action, [:index_name, :database] => :environment do |_, args| task :enqueue_reindexing_action, [:index_name, :database] => :environment do |_, args|
connection = Gitlab::Database.databases[args.fetch(:database, Gitlab::Database::PRIMARY_DATABASE_NAME)] model = Gitlab::Database.database_base_models[args.fetch(:database, Gitlab::Database::PRIMARY_DATABASE_NAME)]
Gitlab::Database::SharedModel.using_connection(connection.scope.connection) do Gitlab::Database::SharedModel.using_connection(model.connection) do
queued_action = Gitlab::Database::PostgresIndex.find(args[:index_name]).queued_reindexing_actions.create! queued_action = Gitlab::Database::PostgresIndex.find(args[:index_name]).queued_reindexing_actions.create!
puts "Queued reindexing action: #{queued_action}" puts "Queued reindexing action: #{queued_action}"
......
...@@ -68,8 +68,8 @@ namespace :gitlab do ...@@ -68,8 +68,8 @@ namespace :gitlab do
puts "Version:\t#{Gitlab::VERSION}" puts "Version:\t#{Gitlab::VERSION}"
puts "Revision:\t#{Gitlab.revision}" puts "Revision:\t#{Gitlab.revision}"
puts "Directory:\t#{Rails.root}" puts "Directory:\t#{Rails.root}"
puts "DB Adapter:\t#{Gitlab::Database.main.human_adapter_name}" puts "DB Adapter:\t#{ApplicationRecord.database.human_adapter_name}"
puts "DB Version:\t#{Gitlab::Database.main.version}" puts "DB Version:\t#{ApplicationRecord.database.version}"
puts "URL:\t\t#{Gitlab.config.gitlab.url}" puts "URL:\t\t#{Gitlab.config.gitlab.url}"
puts "HTTP Clone URL:\t#{http_clone_url}" puts "HTTP Clone URL:\t#{http_clone_url}"
puts "SSH Clone URL:\t#{ssh_clone_url}" puts "SSH Clone URL:\t#{ssh_clone_url}"
......
...@@ -170,7 +170,7 @@ namespace :gitlab do ...@@ -170,7 +170,7 @@ namespace :gitlab do
inverval = (ENV['MAX_DATABASE_CONNECTION_CHECK_INTERVAL'] || 10).to_f inverval = (ENV['MAX_DATABASE_CONNECTION_CHECK_INTERVAL'] || 10).to_f
attempts.to_i.times do attempts.to_i.times do
unless Gitlab::Database.main.exists? unless ApplicationRecord.database.exists?
puts "Waiting until database is ready before continuing...".color(:yellow) puts "Waiting until database is ready before continuing...".color(:yellow)
sleep inverval sleep inverval
end end
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
module RuboCop module RuboCop
module Cop module Cop
module Gitlab module Gitlab
# Cop that disallows the use of `Gitlab::Database.main.bulk_insert`, in favour of using # Cop that disallows the use of `legacy_bulk_insert`, in favour of using
# the `BulkInsertSafe` module. # the `BulkInsertSafe` module.
class BulkInsert < RuboCop::Cop::Cop class BulkInsert < RuboCop::Cop::Cop
MSG = 'Use the `BulkInsertSafe` concern, instead of using `Gitlab::Database.main.bulk_insert`. See https://docs.gitlab.com/ee/development/insert_into_tables_in_batches.html' MSG = 'Use the `BulkInsertSafe` concern, instead of using `LegacyBulkInsert.bulk_insert`. See https://docs.gitlab.com/ee/development/insert_into_tables_in_batches.html'
def_node_matcher :raw_union?, <<~PATTERN def_node_matcher :raw_union?, <<~PATTERN
(send (send (const (const _ :Gitlab) :Database) :main) :bulk_insert ...) (send _ :legacy_bulk_insert ...)
PATTERN PATTERN
def on_send(node) def on_send(node)
......
...@@ -39,7 +39,7 @@ FactoryBot.define do ...@@ -39,7 +39,7 @@ FactoryBot.define do
sha = commit_version[action] sha = commit_version[action]
version = DesignManagement::Version.new(sha: sha, issue: issue, author: evaluator.author) version = DesignManagement::Version.new(sha: sha, issue: issue, author: evaluator.author)
version.save!(validate: false) # We need it to have an ID, validate later version.save!(validate: false) # We need it to have an ID, validate later
Gitlab::Database.main.bulk_insert(dv_table_name, [action.row_attrs(version)]) # rubocop:disable Gitlab/BulkInsert ApplicationRecord.legacy_bulk_insert(dv_table_name, [action.row_attrs(version)]) # rubocop:disable Gitlab/BulkInsert
end end
# always a creation # always a creation
......
...@@ -8,11 +8,11 @@ RSpec.describe 'Database config initializer', :reestablished_active_record_base ...@@ -8,11 +8,11 @@ RSpec.describe 'Database config initializer', :reestablished_active_record_base
end end
it 'retains the correct database name for the connection' do it 'retains the correct database name for the connection' do
previous_db_name = Gitlab::Database.main.scope.connection.pool.db_config.name previous_db_name = ApplicationRecord.connection.pool.db_config.name
subject subject
expect(Gitlab::Database.main.scope.connection.pool.db_config.name).to eq(previous_db_name) expect(ApplicationRecord.connection.pool.db_config.name).to eq(previous_db_name)
end end
it 'does not overwrite custom pool settings' do it 'does not overwrite custom pool settings' do
......
...@@ -78,7 +78,9 @@ RSpec.describe Feature::Gitaly do ...@@ -78,7 +78,9 @@ RSpec.describe Feature::Gitaly do
context 'when table does not exist' do context 'when table does not exist' do
before do before do
allow(::Gitlab::Database.main).to receive(:cached_table_exists?).and_return(false) allow(Feature::FlipperFeature.database)
.to receive(:cached_table_exists?)
.and_return(false)
end end
it 'returns an empty Hash' do it 'returns an empty Hash' do
......
...@@ -314,7 +314,7 @@ RSpec.describe Feature, stub_feature_flags: false do ...@@ -314,7 +314,7 @@ RSpec.describe Feature, stub_feature_flags: false do
context 'when database exists' do context 'when database exists' do
before do before do
allow(Gitlab::Database.main).to receive(:exists?).and_return(true) allow(ApplicationRecord.database).to receive(:exists?).and_return(true)
end end
it 'checks the persisted status and returns false' do it 'checks the persisted status and returns false' do
...@@ -326,7 +326,7 @@ RSpec.describe Feature, stub_feature_flags: false do ...@@ -326,7 +326,7 @@ RSpec.describe Feature, stub_feature_flags: false do
context 'when database does not exist' do context 'when database does not exist' do
before do before do
allow(Gitlab::Database.main).to receive(:exists?).and_return(false) allow(ApplicationRecord.database).to receive(:exists?).and_return(false)
end end
it 'returns false without checking the status in the database' do it 'returns false without checking the status in the database' do
......
...@@ -42,7 +42,7 @@ RSpec.describe Gitlab::BackgroundMigration::JobCoordinator do ...@@ -42,7 +42,7 @@ RSpec.describe Gitlab::BackgroundMigration::JobCoordinator do
describe '#with_shared_connection' do describe '#with_shared_connection' do
it 'yields to the block after properly configuring SharedModel' do it 'yields to the block after properly configuring SharedModel' do
expect(Gitlab::Database::SharedModel).to receive(:using_connection) expect(Gitlab::Database::SharedModel).to receive(:using_connection)
.with(Gitlab::Database.main.scope.connection).and_yield .with(ActiveRecord::Base.connection).and_yield
expect { |b| coordinator.with_shared_connection(&b) }.to yield_with_no_args expect { |b| coordinator.with_shared_connection(&b) }.to yield_with_no_args
end end
......
...@@ -8,7 +8,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do ...@@ -8,7 +8,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
context 'when database meets minimum supported version' do context 'when database meets minimum supported version' do
before do before do
allow(Gitlab::Database.main).to receive(:postgresql_minimum_supported_version?).and_return(true) allow(ApplicationRecord.database).to receive(:postgresql_minimum_supported_version?).and_return(true)
end end
it { is_expected.to be_empty } it { is_expected.to be_empty }
...@@ -16,7 +16,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do ...@@ -16,7 +16,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
context 'when database does not meet minimum supported version' do context 'when database does not meet minimum supported version' do
before do before do
allow(Gitlab::Database.main).to receive(:postgresql_minimum_supported_version?).and_return(false) allow(ApplicationRecord.database).to receive(:postgresql_minimum_supported_version?).and_return(false)
end end
let(:notice_deprecated_database) do let(:notice_deprecated_database) do
...@@ -26,7 +26,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do ...@@ -26,7 +26,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
'%{pg_version_minimum} is required for this version of GitLab. ' \ '%{pg_version_minimum} is required for this version of GitLab. ' \
'Please upgrade your environment to a supported PostgreSQL version, ' \ 'Please upgrade your environment to a supported PostgreSQL version, ' \
'see %{pg_requirements_url} for details.') % { 'see %{pg_requirements_url} for details.') % {
pg_version_current: Gitlab::Database.main.version, pg_version_current: ApplicationRecord.database.version,
pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION, pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
pg_requirements_url: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>' pg_requirements_url: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>'
} }
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Database::EachDatabase do RSpec.describe Gitlab::Database::EachDatabase do
describe '.each_database' do describe '.each_database_connection' do
let(:expected_connections) do let(:expected_connections) do
Gitlab::Database.databases.map { |name, wrapper| [wrapper.scope.connection, name] } Gitlab::Database.database_base_models.map { |name, model| [model.connection, name] }
end end
it 'yields each connection after connecting SharedModel' do it 'yields each connection after connecting SharedModel' do
......
...@@ -542,4 +542,17 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do ...@@ -542,4 +542,17 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end end
end end
end end
describe '#wal_diff' do
it 'returns the diff between two write locations' do
loc1 = lb.send(:get_write_location, lb.pool.connection)
create(:user) # This ensures we get a new WAL location
loc2 = lb.send(:get_write_location, lb.pool.connection)
diff = lb.wal_diff(loc2, loc1)
expect(diff).to be_positive
end
end
end end
...@@ -195,7 +195,7 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do ...@@ -195,7 +195,7 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
end end
# Postgres 11 does not support foreign keys to partitioned tables # Postgres 11 does not support foreign keys to partitioned tables
if Gitlab::Database.main.version.to_f >= 12 if ApplicationRecord.database.version.to_f >= 12
context 'when the model is the target of a foreign key' do context 'when the model is the target of a foreign key' do
before do before do
connection.execute(<<~SQL) connection.execute(<<~SQL)
......
...@@ -15,13 +15,6 @@ RSpec.describe Gitlab::Database do ...@@ -15,13 +15,6 @@ RSpec.describe Gitlab::Database do
end end
end end
describe '.databases' do
it 'stores connections as a HashWithIndifferentAccess' do
expect(described_class.databases.has_key?('main')).to be true
expect(described_class.databases.has_key?(:main)).to be true
end
end
describe '.default_pool_size' do describe '.default_pool_size' do
before do before do
allow(Gitlab::Runtime).to receive(:max_threads).and_return(7) allow(Gitlab::Runtime).to receive(:max_threads).and_return(7)
...@@ -112,18 +105,30 @@ RSpec.describe Gitlab::Database do ...@@ -112,18 +105,30 @@ RSpec.describe Gitlab::Database do
end end
describe '.check_postgres_version_and_print_warning' do describe '.check_postgres_version_and_print_warning' do
let(:reflect) { instance_spy(Gitlab::Database::Reflection) }
subject { described_class.check_postgres_version_and_print_warning } subject { described_class.check_postgres_version_and_print_warning }
before do
allow(Gitlab::Database::Reflection)
.to receive(:new)
.and_return(reflect)
end
it 'prints a warning if not compliant with minimum postgres version' do it 'prints a warning if not compliant with minimum postgres version' do
allow(described_class.main).to receive(:postgresql_minimum_supported_version?).and_return(false) allow(reflect).to receive(:postgresql_minimum_supported_version?).and_return(false)
expect(Kernel).to receive(:warn).with(/You are using PostgreSQL/) expect(Kernel)
.to receive(:warn)
.with(/You are using PostgreSQL/)
.exactly(Gitlab::Database.database_base_models.length)
.times
subject subject
end end
it 'doesnt print a warning if compliant with minimum postgres version' do it 'doesnt print a warning if compliant with minimum postgres version' do
allow(described_class.main).to receive(:postgresql_minimum_supported_version?).and_return(true) allow(reflect).to receive(:postgresql_minimum_supported_version?).and_return(true)
expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/) expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/)
...@@ -131,7 +136,7 @@ RSpec.describe Gitlab::Database do ...@@ -131,7 +136,7 @@ RSpec.describe Gitlab::Database do
end end
it 'doesnt print a warning in Rails runner environment' do it 'doesnt print a warning in Rails runner environment' do
allow(described_class.main).to receive(:postgresql_minimum_supported_version?).and_return(false) allow(reflect).to receive(:postgresql_minimum_supported_version?).and_return(false)
allow(Gitlab::Runtime).to receive(:rails_runner?).and_return(true) allow(Gitlab::Runtime).to receive(:rails_runner?).and_return(true)
expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/) expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/)
...@@ -140,13 +145,13 @@ RSpec.describe Gitlab::Database do ...@@ -140,13 +145,13 @@ RSpec.describe Gitlab::Database do
end end
it 'ignores ActiveRecord errors' do it 'ignores ActiveRecord errors' do
allow(described_class.main).to receive(:postgresql_minimum_supported_version?).and_raise(ActiveRecord::ActiveRecordError) allow(reflect).to receive(:postgresql_minimum_supported_version?).and_raise(ActiveRecord::ActiveRecordError)
expect { subject }.not_to raise_error expect { subject }.not_to raise_error
end end
it 'ignores Postgres errors' do it 'ignores Postgres errors' do
allow(described_class.main).to receive(:postgresql_minimum_supported_version?).and_raise(PG::Error) allow(reflect).to receive(:postgresql_minimum_supported_version?).and_raise(PG::Error)
expect { subject }.not_to raise_error expect { subject }.not_to raise_error
end end
......
...@@ -116,13 +116,13 @@ RSpec.describe Gitlab::GithubImport::BulkImporting do ...@@ -116,13 +116,13 @@ RSpec.describe Gitlab::GithubImport::BulkImporting do
value: 5 value: 5
) )
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.ordered .ordered
.with('kittens', rows.first(5)) .with('kittens', rows.first(5))
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.ordered .ordered
.with('kittens', rows.last(5)) .with('kittens', rows.last(5))
......
...@@ -82,8 +82,8 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail ...@@ -82,8 +82,8 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail
it 'does not import the note when a foreign key error is raised' do it 'does not import the note when a foreign key error is raised' do
stub_user_finder(project.creator_id, false) stub_user_finder(project.creator_id, false)
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key') .and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
expect { subject.execute } expect { subject.execute }
...@@ -94,6 +94,8 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail ...@@ -94,6 +94,8 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail
describe '#execute' do describe '#execute' do
context 'when the merge request no longer exists' do context 'when the merge request no longer exists' do
it 'does not import anything' do it 'does not import anything' do
expect(ApplicationRecord).not_to receive(:legacy_bulk_insert)
expect { subject.execute } expect { subject.execute }
.to not_change(DiffNote, :count) .to not_change(DiffNote, :count)
.and not_change(LegacyDiffNote, :count) .and not_change(LegacyDiffNote, :count)
......
...@@ -190,8 +190,8 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi ...@@ -190,8 +190,8 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
.with(issue.assignees[1]) .with(issue.assignees[1])
.and_return(5) .and_return(5)
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.with( .with(
IssueAssignee.table_name, IssueAssignee.table_name,
[{ issue_id: 1, user_id: 4 }, { issue_id: 1, user_id: 5 }] [{ issue_id: 1, user_id: 4 }, { issue_id: 1, user_id: 5 }]
......
...@@ -39,8 +39,8 @@ RSpec.describe Gitlab::GithubImport::Importer::LabelLinksImporter do ...@@ -39,8 +39,8 @@ RSpec.describe Gitlab::GithubImport::Importer::LabelLinksImporter do
.and_return(1) .and_return(1)
freeze_time do freeze_time do
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.with( .with(
LabelLink.table_name, LabelLink.table_name,
[ [
...@@ -64,8 +64,8 @@ RSpec.describe Gitlab::GithubImport::Importer::LabelLinksImporter do ...@@ -64,8 +64,8 @@ RSpec.describe Gitlab::GithubImport::Importer::LabelLinksImporter do
.with('bug') .with('bug')
.and_return(nil) .and_return(nil)
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.with(LabelLink.table_name, []) .with(LabelLink.table_name, [])
importer.create_labels importer.create_labels
......
...@@ -41,8 +41,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do ...@@ -41,8 +41,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
.with(github_note) .with(github_note)
.and_return([user.id, true]) .and_return([user.id, true])
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.with( .with(
Note.table_name, Note.table_name,
[ [
...@@ -71,8 +71,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do ...@@ -71,8 +71,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
.with(github_note) .with(github_note)
.and_return([project.creator_id, false]) .and_return([project.creator_id, false])
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.with( .with(
Note.table_name, Note.table_name,
[ [
...@@ -115,7 +115,7 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do ...@@ -115,7 +115,7 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
context 'when the noteable does not exist' do context 'when the noteable does not exist' do
it 'does not import the note' do it 'does not import the note' do
expect(Gitlab::Database.main).not_to receive(:bulk_insert) expect(ApplicationRecord).not_to receive(:legacy_bulk_insert)
importer.execute importer.execute
end end
...@@ -134,8 +134,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do ...@@ -134,8 +134,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
.with(github_note) .with(github_note)
.and_return([user.id, true]) .and_return([user.id, true])
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key') .and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
expect { importer.execute }.not_to raise_error expect { importer.execute }.not_to raise_error
......
...@@ -16,8 +16,8 @@ RSpec.describe Gitlab::Import::DatabaseHelpers do ...@@ -16,8 +16,8 @@ RSpec.describe Gitlab::Import::DatabaseHelpers do
let(:project) { create(:project) } let(:project) { create(:project) }
it 'returns the ID returned by the query' do it 'returns the ID returned by the query' do
expect(Gitlab::Database.main) expect(ApplicationRecord)
.to receive(:bulk_insert) .to receive(:legacy_bulk_insert)
.with(Issue.table_name, [attributes], return_ids: true) .with(Issue.table_name, [attributes], return_ids: true)
.and_return([10]) .and_return([10])
......
...@@ -18,8 +18,8 @@ RSpec.describe Gitlab::Metrics::Samplers::DatabaseSampler do ...@@ -18,8 +18,8 @@ RSpec.describe Gitlab::Metrics::Samplers::DatabaseSampler do
let(:labels) do let(:labels) do
{ {
class: 'ActiveRecord::Base', class: 'ActiveRecord::Base',
host: Gitlab::Database.main.config['host'], host: ApplicationRecord.database.config['host'],
port: Gitlab::Database.main.config['port'] port: ApplicationRecord.database.config['port']
} }
end end
......
...@@ -7,18 +7,18 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GenericMetric do ...@@ -7,18 +7,18 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GenericMetric do
subject do subject do
Class.new(described_class) do Class.new(described_class) do
fallback(custom_fallback) fallback(custom_fallback)
value { Gitlab::Database.main.version } value { ApplicationRecord.database.version }
end.new(time_frame: 'none') end.new(time_frame: 'none')
end end
describe '#value' do describe '#value' do
it 'gives the correct value' do it 'gives the correct value' do
expect(subject.value).to eq(Gitlab::Database.main.version) expect(subject.value).to eq(ApplicationRecord.database.version)
end end
context 'when raising an exception' do context 'when raising an exception' do
it 'return the custom fallback' do it 'return the custom fallback' do
expect(Gitlab::Database.main).to receive(:version).and_raise('Error') expect(ApplicationRecord.database).to receive(:version).and_raise('Error')
expect(subject.value).to eq(custom_fallback) expect(subject.value).to eq(custom_fallback)
end end
end end
...@@ -28,18 +28,18 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GenericMetric do ...@@ -28,18 +28,18 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GenericMetric do
context 'with default fallback' do context 'with default fallback' do
subject do subject do
Class.new(described_class) do Class.new(described_class) do
value { Gitlab::Database.main.version } value { ApplicationRecord.database.version }
end.new(time_frame: 'none') end.new(time_frame: 'none')
end end
describe '#value' do describe '#value' do
it 'gives the correct value' do it 'gives the correct value' do
expect(subject.value).to eq(Gitlab::Database.main.version ) expect(subject.value).to eq(ApplicationRecord.database.version )
end end
context 'when raising an exception' do context 'when raising an exception' do
it 'return the default fallback' do it 'return the default fallback' do
expect(Gitlab::Database.main).to receive(:version).and_raise('Error') expect(ApplicationRecord.database).to receive(:version).and_raise('Error')
expect(subject.value).to eq(described_class::FALLBACK) expect(subject.value).to eq(described_class::FALLBACK)
end end
end end
......
...@@ -978,9 +978,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -978,9 +978,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(subject[:gitlab_pages][:enabled]).to eq(Gitlab.config.pages.enabled) expect(subject[:gitlab_pages][:enabled]).to eq(Gitlab.config.pages.enabled)
expect(subject[:gitlab_pages][:version]).to eq(Gitlab::Pages::VERSION) expect(subject[:gitlab_pages][:version]).to eq(Gitlab::Pages::VERSION)
expect(subject[:git][:version]).to eq(Gitlab::Git.version) expect(subject[:git][:version]).to eq(Gitlab::Git.version)
expect(subject[:database][:adapter]).to eq(Gitlab::Database.main.adapter_name) expect(subject[:database][:adapter]).to eq(ApplicationRecord.database.adapter_name)
expect(subject[:database][:version]).to eq(Gitlab::Database.main.version) expect(subject[:database][:version]).to eq(ApplicationRecord.database.version)
expect(subject[:database][:pg_system_id]).to eq(Gitlab::Database.main.system_id) expect(subject[:database][:pg_system_id]).to eq(ApplicationRecord.database.system_id)
expect(subject[:mail][:smtp_server]).to eq(ActionMailer::Base.smtp_settings[:address]) expect(subject[:mail][:smtp_server]).to eq(ActionMailer::Base.smtp_settings[:address])
expect(subject[:gitaly][:version]).to be_present expect(subject[:gitaly][:version]).to be_present
expect(subject[:gitaly][:servers]).to be >= 1 expect(subject[:gitaly][:servers]).to be >= 1
......
...@@ -3806,7 +3806,7 @@ RSpec.describe Ci::Build do ...@@ -3806,7 +3806,7 @@ RSpec.describe Ci::Build do
it 'ensures that it is not run in database transaction' do it 'ensures that it is not run in database transaction' do
expect(job.pipeline.persistent_ref).to receive(:create) do expect(job.pipeline.persistent_ref).to receive(:create) do
expect(Gitlab::Database.main).not_to be_inside_transaction expect(ApplicationRecord).not_to be_inside_transaction
end end
run_job_without_exception run_job_without_exception
......
...@@ -182,7 +182,7 @@ RSpec.describe BulkInsertSafe do ...@@ -182,7 +182,7 @@ RSpec.describe BulkInsertSafe do
context 'with returns option set' do context 'with returns option set' do
let(:items) { bulk_insert_item_class.valid_list(1) } let(:items) { bulk_insert_item_class.valid_list(1) }
subject(:bulk_insert) { bulk_insert_item_class.bulk_insert!(items, returns: returns) } subject(:legacy_bulk_insert) { bulk_insert_item_class.bulk_insert!(items, returns: returns) }
context 'when is set to :ids' do context 'when is set to :ids' do
let(:returns) { :ids } let(:returns) { :ids }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DatabaseReflection do
describe '.reflect' do
it 'returns a Reflection instance' do
expect(User.database).to be_an_instance_of(Gitlab::Database::Reflection)
end
it 'memoizes the result' do
instance1 = User.database
instance2 = User.database
expect(instance1).to equal(instance2)
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment