Commit 9bb48c9b authored by Yorick Peterse's avatar Yorick Peterse

Split Database::Connection into separate types

Gitlab::Database::Connection was a kitchen sink type of class: it had
methods for obtaining database information (e.g. the database name),
running WAL related queries, querying the status of transactions, and
more.

This commit splits this class into separate types. For example,
reflection related methods are now located in
Gitlab::Database::Reflection. Transaction related methods are moved into
the Transactions module. The method to get a WAL diff has been moved
into the LoadBalancer class.

With this also changes the use of these methods. For example, instead of
this:

    Gitlab::Database.main.cached_column_exists?(:users, :id)

You now write this:

    Users.database.cached_column_exists?(:id)

Apart from being shorter in many cases, it also decouples the code using
these methods from the main database. This makes it easier to support
multiple databases over time.
parent 2edef5d4
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base
include DatabaseReflection
include Transactions
include LegacyBulkInsert
self.abstract_class = true
alias_method :reset, :reload
......
......@@ -244,11 +244,11 @@ module ApplicationSettingImplementation
end
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
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
def disabled_oauth_sign_in_sources=(sources)
......
......@@ -127,7 +127,7 @@ module CascadingNamespaceSettingAttribute
end
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
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
end
def database_exists?
Gitlab::Database.main.exists?
database.exists?
end
end
end
......@@ -32,7 +32,7 @@ module ShaAttribute
end
def database_exists?
Gitlab::Database.main.exists?
database.exists?
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
end
def database_exists?
Gitlab::Database.main.exists?
database.exists?
end
end
end
......@@ -300,7 +300,7 @@ class Deployment < ApplicationRecord
"#{id} as deployment_id",
"#{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.
#
# We also ignore any duplicates so this method can be called multiple times
......
......@@ -88,7 +88,7 @@ module DesignManagement
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.validate!
design_actions.each(&:performed)
......
......@@ -26,7 +26,7 @@ class MergeRequestContextCommit < ApplicationRecord
# create MergeRequestContextCommit by given commit sha and it's diff file record
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
def to_commit
......
......@@ -14,7 +14,7 @@ class MergeRequestContextCommitDiffFile < ApplicationRecord
# create MergeRequestContextCommitDiffFile by given diff file record(s)
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
def path
......
......@@ -515,7 +515,7 @@ class MergeRequestDiff < ApplicationRecord
transaction do
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!
end
......@@ -535,7 +535,7 @@ class MergeRequestDiff < ApplicationRecord
transaction do
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)
end
......@@ -595,7 +595,7 @@ class MergeRequestDiff < ApplicationRecord
rows = build_external_merge_request_diff_files(rows) if use_external_diff?
# 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
def build_external_diff_tempfile(rows)
......
......@@ -74,7 +74,7 @@ class MergeRequestDiffCommit < ApplicationRecord
)
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
def self.prepare_commits_for_bulk_insert(commits)
......
......@@ -181,12 +181,12 @@ module DesignManagement
)
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.
# When this is fixed, we can remove the call to
# `with_project_iid_supply` above, since the objects will be instantiated
# 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,
new_rows,
return_ids: true
......@@ -207,9 +207,9 @@ module DesignManagement
)
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.
::Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert
::ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
DesignManagement::Version.table_name,
new_rows,
return_ids: true
......@@ -239,7 +239,7 @@ module DesignManagement
end
# 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,
new_rows
)
......@@ -278,7 +278,7 @@ module DesignManagement
# We cannot use `BulkInsertSafe` due to the LfsObjectsProject#update_project_statistics
# 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,
new_rows,
on_conflict: :do_nothing # Upsert
......
......@@ -99,7 +99,7 @@ module Issuable
yield(event)
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
......
# frozen_string_literal: true
module Packages
# rubocop: disable Gitlab/BulkInsert
class CreateDependencyService < BaseService
attr_reader :package, :dependencies
......@@ -51,7 +52,7 @@ module Packages
}
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
Packages::Dependency.uncached do
......@@ -72,11 +73,8 @@ module Packages
}
end
database.bulk_insert(Packages::DependencyLink.table_name, rows)
end
def database
::Gitlab::Database.main
ApplicationRecord.legacy_bulk_insert(Packages::DependencyLink.table_name, rows)
end
end
# rubocop: enable Gitlab/BulkInsert
end
......@@ -41,7 +41,7 @@ module Packages
}
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
def raw_dependency_for(dependency)
......
......@@ -15,7 +15,7 @@ module Packages
tags_to_create = @tags - existing_tags
@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
private
......
......@@ -21,7 +21,7 @@ module Projects
.update_all(share: update[:share])
end
Gitlab::Database.main.bulk_insert( # rubocop:disable Gitlab/BulkInsert
ApplicationRecord.legacy_bulk_insert( # rubocop:disable Gitlab/BulkInsert
RepositoryLanguage.table_name,
detection.insertions(matching_programming_languages)
)
......
......@@ -38,7 +38,7 @@ module Projects
rows = existent_lfs_objects
.not_linked_to_project(project)
.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
linked_existing_objects += existent_lfs_objects.map(&:oid)
......
......@@ -23,7 +23,7 @@ module ResourceEvents
label_hash.merge(label_id: label.id, action: ResourceLabelEvent.actions['remove'])
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
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_label_changed_action(author: user) if resource.is_a?(Issue)
......
......@@ -25,7 +25,7 @@ module Suggestions
end
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
Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_add_suggestion_action(note: @note)
......
......@@ -153,9 +153,9 @@
%span.float-right
#{Rails::VERSION::STRING}
%p
= Gitlab::Database.main.human_adapter_name
= ApplicationRecord.database.human_adapter_name
%span.float-right
= Gitlab::Database.main.version
= ApplicationRecord.database.version
%p
= _('Redis')
%span.float-right
......
......@@ -54,7 +54,7 @@ module Gitlab
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
def assign_issue(project_id, issue_id, assignee_ids)
......@@ -62,7 +62,7 @@ module Gitlab
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
def build_label_attrs(issue_id, label_id)
......
# frozen_string_literal: true
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
......@@ -14,7 +14,7 @@ end
if defined?(ActiveRecord::Base)
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"
# as there's no need for the master process to hold a connection
......
......@@ -10,8 +10,8 @@ if Gitlab::Runtime.console?
puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision}) #{Gitlab.ee? ? 'EE' : 'FOSS'}"
puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.version)}"
if Gitlab::Database.main.exists?
puts " #{Gitlab::Database.main.human_adapter_name}:".ljust(justify) + Gitlab::Database.main.version
if ApplicationRecord.database.exists?
puts " #{ApplicationRecord.database.human_adapter_name}:".ljust(justify) + ApplicationRecord.database.version
Gitlab.ee do
if Gitlab::Geo.connected? && Gitlab::Geo.enabled?
......
......@@ -20,7 +20,7 @@ module Sidekiq
module NoEnqueueingFromTransactions
%i(perform_async perform_at perform_in).each do |name|
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
raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG
`#{self}.#{name}` cannot be called inside a transaction as this can lead to
......
......@@ -20,7 +20,7 @@ class MigrateSamlIdentitiesToScimIdentities < ActiveRecord::Migration[6.0]
record.attributes.extract!("extern_uid", "user_id", "group_id", "active", "created_at", "updated_at")
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
......
......@@ -12,13 +12,13 @@ necessary to add database (version) specific behavior.
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`.
This allows you to write code such as:
```ruby
if Gitlab::Database.main.version.to_f >= 11.7
if ApplicationRecord.database.version.to_f >= 11.7
run_really_fast_query
else
run_fast_query
......
......@@ -421,19 +421,19 @@ module EE
end
def elasticsearch_indexing_column_exists?
::Gitlab::Database.main.cached_column_exists?(:application_settings, :elasticsearch_indexing)
self.class.database.cached_column_exists?(:elasticsearch_indexing)
end
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
def elasticsearch_search_column_exists?
::Gitlab::Database.main.cached_column_exists?(:application_settings, :elasticsearch_search)
self.class.database.cached_column_exists?(:elasticsearch_search)
end
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
def check_geo_node_allowed_ips
......
......@@ -37,7 +37,7 @@ class ElasticsearchIndexedNamespace < ApplicationRecord
{ created_at: now, updated_at: now, namespace_id: id }
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!
jobs = batch_ids.map { |id| [id, :index] }
......
......@@ -18,7 +18,7 @@ module AuditEvents
return if collection.empty?
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)
end
......
......@@ -15,7 +15,7 @@ module Iterations
return ::ServiceResponse.error(message: _('Cadence is not automated'), http_status: 422) unless cadence.can_be_automated?
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)
......
......@@ -20,8 +20,8 @@ module Iterations
ApplicationRecord.transaction do
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
Gitlab::Database.main.bulk_insert(ResourceIterationEvent.table_name, add_iteration_events) # rubocop:disable Gitlab/BulkInsert
ApplicationRecord.legacy_bulk_insert(ResourceIterationEvent.table_name, remove_iteration_events) # rubocop:disable Gitlab/BulkInsert
ApplicationRecord.legacy_bulk_insert(ResourceIterationEvent.table_name, add_iteration_events) # rubocop:disable Gitlab/BulkInsert
end
end
......
......@@ -10,7 +10,7 @@ module ResourceEvents
end
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
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_weight_changed_action(author: user)
......
......@@ -163,7 +163,7 @@ module Geo
def update_pending_resources
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?
end
end
......
......@@ -49,7 +49,7 @@ module EE
}
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
......
# 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
# This method can be called/loaded before the database
# has been created. With this guard clause we prevent querying
# 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)
[::Gitlab::ImportSources::ImportSource.new('gitlab_custom_project_template',
......
......@@ -83,7 +83,7 @@ module EE
action = route_hash[:action]
if ALLOWLISTED_GEO_ROUTES[controller]&.include?(action)
::Gitlab::Database.main.db_read_write?
::ApplicationRecord.database.db_read_write?
else
ALLOWLISTED_GEO_ROUTES_TRACKING_DB[controller]&.include?(action)
end
......
......@@ -213,5 +213,17 @@ module Gitlab
[1, capacity].max # at least 1
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
......@@ -9,7 +9,7 @@ module Gitlab
return '' unless Gitlab::Geo.secondary?
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 '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 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
before do
allow(Gitlab::Geo).to receive(:secondary?) { true }
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
context 'when the Geo tracking DB is not configured' do
......@@ -124,8 +124,8 @@ RSpec.describe Gitlab::Geo::HealthCheck, :geo do
describe '#db_replication_lag_seconds' 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'
allow(Gitlab::Database.main).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_receive_lsn).and_return('pg_last_wal_receive_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 }])
end
......
......@@ -426,4 +426,51 @@ RSpec.describe Gitlab::Geo, :geo, :request_store do
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
......@@ -15,7 +15,7 @@ module AfterCommitQueue
end
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)
run_after_commit(&block)
else
......
......@@ -6,6 +6,8 @@ require 'flipper/adapters/active_support_cache_store'
class Feature
# Classes to override flipper table names
class FlipperFeature < Flipper::Adapters::ActiveRecord::Feature
include DatabaseReflection
# Using `self.table_name` won't work. ActiveRecord bug?
superclass.table_name = 'features'
......@@ -36,7 +38,7 @@ class Feature
end
def persisted_names
return [] unless Gitlab::Database.main.exists?
return [] unless ApplicationRecord.database.exists?
# This loads names of all stored feature flags
# and returns a stable Set in the following order:
......@@ -73,7 +75,7 @@ class Feature
# During setup the database does not exist yet. So we haven't stored a value
# 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)
......
......@@ -15,7 +15,7 @@ class Feature
def server_feature_flags(project = nil)
# 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
.select { |f| f.start_with?(PREFIX) }
......
......@@ -189,7 +189,7 @@ module Gitlab
end
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
private
......
......@@ -120,7 +120,10 @@ module Gitlab
end
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
......
......@@ -34,7 +34,7 @@ module Gitlab
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}")
......
......@@ -65,7 +65,7 @@ module Gitlab
next if service_ids.empty?
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
return if migrated_ids.empty?
......
......@@ -21,7 +21,7 @@ module Gitlab
}
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
......
......@@ -6,7 +6,7 @@ module Gitlab
extend self
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
'%{pg_version_minimum} is required for this version of GitLab. ' \
'Please upgrade your environment to a supported PostgreSQL version, ' \
'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_requirements_url: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>'
}
......
......@@ -85,7 +85,7 @@ module Gitlab
active_db_connection = ActiveRecord::Base.connection.active? rescue false
active_db_connection &&
Gitlab::Database.main.cached_table_exists?('application_settings')
ApplicationSetting.database.cached_table_exists?
rescue ActiveRecord::NoDatabaseError
false
end
......
......@@ -60,18 +60,7 @@ module Gitlab
# inherit from ApplicationRecord.
main: ::ActiveRecord::Base,
ci: ::Ci::ApplicationRecord.connection_class? ? ::Ci::ApplicationRecord : nil
}.compact.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]
}.compact.with_indifferent_access.freeze
end
# We configure the database connection pool size automatically based on the
......@@ -110,8 +99,10 @@ module Gitlab
def self.check_postgres_version_and_print_warning
return if Gitlab::Runtime.rails_runner?
databases.each do |name, connection|
next if connection.postgresql_minimum_supported_version?
database_base_models.each do |name, model|
database = Gitlab::Database::Reflection.new(model)
next if database.postgresql_minimum_supported_version?
Kernel.warn ERB.new(Rainbow.new.wrap(<<~EOS).red).result
......@@ -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.
<% if Rails.env.development? || Rails.env.test? %>
If using gitlab-development-kit, please find the relevant steps here:
......
......@@ -19,7 +19,7 @@ module Gitlab
# Note: to be deleted after the minimum PG version is set to 12.0
def self.materialized_supported?
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
......
......@@ -5,8 +5,8 @@ module Gitlab
module EachDatabase
class << self
def each_database_connection
Gitlab::Database.databases.each_pair do |connection_name, connection_wrapper|
connection = connection_wrapper.scope.connection
Gitlab::Database.database_base_models.each_pair do |connection_name, model|
connection = model.connection
with_shared_connection(connection, connection_name) do
yield connection, connection_name
......
......@@ -263,6 +263,21 @@ module Gitlab
) || raise(::ActiveRecord::ConnectionNotEstablished)
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
def ensure_caching!
......
......@@ -1272,8 +1272,8 @@ module Gitlab
def check_trigger_permissions!(table)
unless Grant.create_and_execute_trigger?(table)
dbname = Database.main.database_name
user = Database.main.username
dbname = ApplicationRecord.database.database_name
user = ApplicationRecord.database.username
raise <<-EOF
Your database user is not allowed to create, drop, or execute triggers on the
......@@ -1595,8 +1595,8 @@ into similar problems in the future (e.g. when new tables are created).
def create_extension(extension)
execute('CREATE EXTENSION IF NOT EXISTS %s' % extension)
rescue ActiveRecord::StatementInvalid => e
dbname = Database.main.database_name
user = Database.main.username
dbname = ApplicationRecord.database.database_name
user = ApplicationRecord.database.username
warn(<<~MSG) if e.to_s =~ /permission denied/
GitLab requires the PostgreSQL extension '#{extension}' installed in database '#{dbname}', but
......@@ -1623,8 +1623,8 @@ into similar problems in the future (e.g. when new tables are created).
def drop_extension(extension)
execute('DROP EXTENSION IF EXISTS %s' % extension)
rescue ActiveRecord::StatementInvalid => e
dbname = Database.main.database_name
user = Database.main.username
dbname = ApplicationRecord.database.database_name
user = ApplicationRecord.database.username
warn(<<~MSG) if e.to_s =~ /permission denied/
This migration attempts to drop the PostgreSQL extension '#{extension}'
......
......@@ -2,20 +2,14 @@
module Gitlab
module Database
# Configuration settings and methods for interacting with a PostgreSQL
# database, with support for multiple databases.
class Connection
attr_reader :scope
# Initializes a new `Database`.
#
# 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
# 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
@open_transactions_baseline = 0
end
def config
......@@ -28,7 +22,7 @@ module Gitlab
#
# - https://gitlab.com/gitlab-org/release/retrospectives/-/issues/39
# - 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 username
......@@ -58,8 +52,7 @@ module Gitlab
# Check whether the underlying database is in read-only mode
def db_read_only?
pg_is_in_recovery =
scope
.connection
connection
.execute('SELECT pg_is_in_recovery()')
.first
.fetch('pg_is_in_recovery')
......@@ -83,58 +76,14 @@ module Gitlab
version.to_f >= MINIMUM_POSTGRES_VERSION
end
# Bulk inserts a number of rows into a table, optionally returning their
# 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)
def cached_column_exists?(column_name)
connection
.schema_cache.columns_hash(table_name)
.schema_cache.columns_hash(model.table_name)
.has_key?(column_name.to_s)
end
def cached_table_exists?(table_name)
exists? && connection.schema_cache.data_source_exists?(table_name)
def cached_table_exists?
exists? && connection.schema_cache.data_source_exists?(model.table_name)
end
def exists?
......@@ -156,47 +105,11 @@ module Gitlab
row['system_identifier']
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
def connection
scope.connection
model.connection
end
end
end
end
Gitlab::Database::Connection.prepend_mod_with('Gitlab::Database::Connection')
......@@ -30,7 +30,7 @@ module Gitlab
# Bulk inserts the given rows into the database.
def bulk_insert(model, rows, batch_size: 100)
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)
end
......
......@@ -70,7 +70,7 @@ module Gitlab
# 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)
# 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,
system: false,
type: 'LegacyDiffNote',
......
......@@ -75,7 +75,7 @@ module Gitlab
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
......
......@@ -40,7 +40,7 @@ module Gitlab
}
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
def find_target_id
......
......@@ -37,7 +37,7 @@ module Gitlab
# We're using bulk_insert here so we can bypass any validations and
# callbacks. Running these would result in a lot of unnecessary SQL
# 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
# 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.
......
......@@ -11,8 +11,8 @@ module Gitlab
# We use bulk_insert here so we can bypass any queries executed by
# callbacks or validation rules, as doing this wouldn't scale when
# importing very large projects.
result = Gitlab::Database.main # rubocop:disable Gitlab/BulkInsert
.bulk_insert(relation.table_name, [attributes], return_ids: true)
result = ApplicationRecord # rubocop:disable Gitlab/BulkInsert
.legacy_bulk_insert(relation.table_name, [attributes], return_ids: true)
result.first
end
......
......@@ -18,7 +18,7 @@ module Gitlab
end
# Newly detected languages, returned in a structure accepted by
# Gitlab::Database.main.bulk_insert
# ApplicationRecord.legacy_bulk_insert
def insertions(programming_languages)
lang_to_id = programming_languages.to_h { |p| [p.name, p.id] }
......
......@@ -208,7 +208,12 @@ module Gitlab
end
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
def strategy
......
......@@ -296,9 +296,11 @@ module Gitlab
version: alt_usage_data(fallback: nil) { Gitlab::CurrentSettings.container_registry_version }
},
database: {
adapter: alt_usage_data { Gitlab::Database.main.adapter_name },
version: alt_usage_data { Gitlab::Database.main.version },
pg_system_id: alt_usage_data { Gitlab::Database.main.system_id }
# rubocop: disable UsageData/LargeTable
adapter: alt_usage_data { ApplicationRecord.database.adapter_name },
version: alt_usage_data { ApplicationRecord.database.version },
pg_system_id: alt_usage_data { ApplicationRecord.database.system_id }
# rubocop: enable UsageData/LargeTable
},
mail: {
smtp_server: alt_usage_data { ActionMailer::Base.smtp_settings[:address] }
......
......@@ -182,9 +182,9 @@ namespace :gitlab do
desc 'Enqueue an index for reindexing'
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!
puts "Queued reindexing action: #{queued_action}"
......
......@@ -68,8 +68,8 @@ namespace :gitlab do
puts "Version:\t#{Gitlab::VERSION}"
puts "Revision:\t#{Gitlab.revision}"
puts "Directory:\t#{Rails.root}"
puts "DB Adapter:\t#{Gitlab::Database.main.human_adapter_name}"
puts "DB Version:\t#{Gitlab::Database.main.version}"
puts "DB Adapter:\t#{ApplicationRecord.database.human_adapter_name}"
puts "DB Version:\t#{ApplicationRecord.database.version}"
puts "URL:\t\t#{Gitlab.config.gitlab.url}"
puts "HTTP Clone URL:\t#{http_clone_url}"
puts "SSH Clone URL:\t#{ssh_clone_url}"
......
......@@ -170,7 +170,7 @@ namespace :gitlab do
inverval = (ENV['MAX_DATABASE_CONNECTION_CHECK_INTERVAL'] || 10).to_f
attempts.to_i.times do
unless Gitlab::Database.main.exists?
unless ApplicationRecord.database.exists?
puts "Waiting until database is ready before continuing...".color(:yellow)
sleep inverval
end
......
......@@ -3,13 +3,13 @@
module RuboCop
module Cop
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.
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
(send (send (const (const _ :Gitlab) :Database) :main) :bulk_insert ...)
(send _ :legacy_bulk_insert ...)
PATTERN
def on_send(node)
......
......@@ -39,7 +39,7 @@ FactoryBot.define do
sha = commit_version[action]
version = DesignManagement::Version.new(sha: sha, issue: issue, author: evaluator.author)
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
# always a creation
......
......@@ -8,11 +8,11 @@ RSpec.describe 'Database config initializer', :reestablished_active_record_base
end
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
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
it 'does not overwrite custom pool settings' do
......
......@@ -78,7 +78,9 @@ RSpec.describe Feature::Gitaly do
context 'when table does not exist' 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
it 'returns an empty Hash' do
......
......@@ -314,7 +314,7 @@ RSpec.describe Feature, stub_feature_flags: false do
context 'when database exists' do
before do
allow(Gitlab::Database.main).to receive(:exists?).and_return(true)
allow(ApplicationRecord.database).to receive(:exists?).and_return(true)
end
it 'checks the persisted status and returns false' do
......@@ -326,7 +326,7 @@ RSpec.describe Feature, stub_feature_flags: false do
context 'when database does not exist' do
before do
allow(Gitlab::Database.main).to receive(:exists?).and_return(false)
allow(ApplicationRecord.database).to receive(:exists?).and_return(false)
end
it 'returns false without checking the status in the database' do
......
......@@ -42,7 +42,7 @@ RSpec.describe Gitlab::BackgroundMigration::JobCoordinator do
describe '#with_shared_connection' do
it 'yields to the block after properly configuring SharedModel' do
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
end
......
......@@ -8,7 +8,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
context 'when database meets minimum supported version' 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
it { is_expected.to be_empty }
......@@ -16,7 +16,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
context 'when database does not meet minimum supported version' 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
let(:notice_deprecated_database) do
......@@ -26,7 +26,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
'%{pg_version_minimum} is required for this version of GitLab. ' \
'Please upgrade your environment to a supported PostgreSQL version, ' \
'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_requirements_url: '<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>'
}
......
......@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::EachDatabase do
describe '.each_database' do
describe '.each_database_connection' 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
it 'yields each connection after connecting SharedModel' do
......
......@@ -542,4 +542,17 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
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
......@@ -195,7 +195,7 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do
end
# 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
before do
connection.execute(<<~SQL)
......
......@@ -15,13 +15,6 @@ RSpec.describe Gitlab::Database do
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
before do
allow(Gitlab::Runtime).to receive(:max_threads).and_return(7)
......@@ -112,18 +105,30 @@ RSpec.describe Gitlab::Database do
end
describe '.check_postgres_version_and_print_warning' do
let(:reflect) { instance_spy(Gitlab::Database::Reflection) }
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
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
end
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/)
......@@ -131,7 +136,7 @@ RSpec.describe Gitlab::Database do
end
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)
expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/)
......@@ -140,13 +145,13 @@ RSpec.describe Gitlab::Database do
end
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
end
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
end
......
......@@ -116,13 +116,13 @@ RSpec.describe Gitlab::GithubImport::BulkImporting do
value: 5
)
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.ordered
.with('kittens', rows.first(5))
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.ordered
.with('kittens', rows.last(5))
......
......@@ -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
stub_user_finder(project.creator_id, false)
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
expect { subject.execute }
......@@ -94,6 +94,8 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail
describe '#execute' do
context 'when the merge request no longer exists' do
it 'does not import anything' do
expect(ApplicationRecord).not_to receive(:legacy_bulk_insert)
expect { subject.execute }
.to not_change(DiffNote, :count)
.and not_change(LegacyDiffNote, :count)
......
......@@ -190,8 +190,8 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
.with(issue.assignees[1])
.and_return(5)
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.with(
IssueAssignee.table_name,
[{ issue_id: 1, user_id: 4 }, { issue_id: 1, user_id: 5 }]
......
......@@ -39,8 +39,8 @@ RSpec.describe Gitlab::GithubImport::Importer::LabelLinksImporter do
.and_return(1)
freeze_time do
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.with(
LabelLink.table_name,
[
......@@ -64,8 +64,8 @@ RSpec.describe Gitlab::GithubImport::Importer::LabelLinksImporter do
.with('bug')
.and_return(nil)
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.with(LabelLink.table_name, [])
importer.create_labels
......
......@@ -41,8 +41,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
.with(github_note)
.and_return([user.id, true])
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.with(
Note.table_name,
[
......@@ -71,8 +71,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
.with(github_note)
.and_return([project.creator_id, false])
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.with(
Note.table_name,
[
......@@ -115,7 +115,7 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
context 'when the noteable does not exist' 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
end
......@@ -134,8 +134,8 @@ RSpec.describe Gitlab::GithubImport::Importer::NoteImporter do
.with(github_note)
.and_return([user.id, true])
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key')
expect { importer.execute }.not_to raise_error
......
......@@ -16,8 +16,8 @@ RSpec.describe Gitlab::Import::DatabaseHelpers do
let(:project) { create(:project) }
it 'returns the ID returned by the query' do
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.with(Issue.table_name, [attributes], return_ids: true)
.and_return([10])
......
......@@ -18,8 +18,8 @@ RSpec.describe Gitlab::Metrics::Samplers::DatabaseSampler do
let(:labels) do
{
class: 'ActiveRecord::Base',
host: Gitlab::Database.main.config['host'],
port: Gitlab::Database.main.config['port']
host: ApplicationRecord.database.config['host'],
port: ApplicationRecord.database.config['port']
}
end
......
......@@ -7,18 +7,18 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GenericMetric do
subject do
Class.new(described_class) do
fallback(custom_fallback)
value { Gitlab::Database.main.version }
value { ApplicationRecord.database.version }
end.new(time_frame: 'none')
end
describe '#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
context 'when raising an exception' 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)
end
end
......@@ -28,18 +28,18 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::GenericMetric do
context 'with default fallback' do
subject do
Class.new(described_class) do
value { Gitlab::Database.main.version }
value { ApplicationRecord.database.version }
end.new(time_frame: 'none')
end
describe '#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
context 'when raising an exception' 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)
end
end
......
......@@ -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][:version]).to eq(Gitlab::Pages::VERSION)
expect(subject[:git][:version]).to eq(Gitlab::Git.version)
expect(subject[:database][:adapter]).to eq(Gitlab::Database.main.adapter_name)
expect(subject[:database][:version]).to eq(Gitlab::Database.main.version)
expect(subject[:database][:pg_system_id]).to eq(Gitlab::Database.main.system_id)
expect(subject[:database][:adapter]).to eq(ApplicationRecord.database.adapter_name)
expect(subject[:database][:version]).to eq(ApplicationRecord.database.version)
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[:gitaly][:version]).to be_present
expect(subject[:gitaly][:servers]).to be >= 1
......
......@@ -3806,7 +3806,7 @@ RSpec.describe Ci::Build do
it 'ensures that it is not run in database transaction' 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
run_job_without_exception
......
......@@ -182,7 +182,7 @@ RSpec.describe BulkInsertSafe do
context 'with returns option set' do
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
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