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
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!
......
......@@ -1260,8 +1260,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
......@@ -1583,8 +1583,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
......@@ -1611,8 +1611,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
# 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
# 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
def initialize(model)
@model = model
@version = nil
@open_transactions_baseline = 0
end
def config
......@@ -28,11 +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
end
def pool_size
config[:pool] || Database.default_pool_size
model.connection_db_config.configuration_hash.with_indifferent_access
end
def username
......@@ -62,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')
......@@ -87,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?
......@@ -160,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)
......
......@@ -2,94 +2,58 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Connection do
let(:connection) { described_class.new }
describe '#config' do
it 'returns a HashWithIndifferentAccess' do
expect(connection.config).to be_an_instance_of(HashWithIndifferentAccess)
end
it 'returns a default pool size' do
expect(connection.config)
.to include(pool: Gitlab::Database.default_pool_size)
end
it 'does not cache its results' do
a = connection.config
b = connection.config
expect(a).not_to equal(b)
end
end
describe '#pool_size' do
context 'when no explicit size is configured' do
it 'returns the default pool size' do
expect(connection).to receive(:config).and_return({ pool: nil })
expect(connection.pool_size).to eq(Gitlab::Database.default_pool_size)
end
end
context 'when an explicit pool size is set' do
it 'returns the pool size' do
expect(connection).to receive(:config).and_return({ pool: 4 })
expect(connection.pool_size).to eq(4)
end
end
end
RSpec.describe Gitlab::Database::Reflection do
let(:database) { described_class.new(ApplicationRecord) }
describe '#username' do
context 'when a username is set' do
it 'returns the username' do
allow(connection).to receive(:config).and_return(username: 'bob')
allow(database).to receive(:config).and_return(username: 'bob')
expect(connection.username).to eq('bob')
expect(database.username).to eq('bob')
end
end
context 'when a username is not set' do
it 'returns the value of the USER environment variable' do
allow(connection).to receive(:config).and_return(username: nil)
allow(database).to receive(:config).and_return(username: nil)
allow(ENV).to receive(:[]).with('USER').and_return('bob')
expect(connection.username).to eq('bob')
expect(database.username).to eq('bob')
end
end
end
describe '#database_name' do
it 'returns the name of the database' do
allow(connection).to receive(:config).and_return(database: 'test')
allow(database).to receive(:config).and_return(database: 'test')
expect(connection.database_name).to eq('test')
expect(database.database_name).to eq('test')
end
end
describe '#adapter_name' do
it 'returns the database adapter name' do
allow(connection).to receive(:config).and_return(adapter: 'test')
allow(database).to receive(:config).and_return(adapter: 'test')
expect(connection.adapter_name).to eq('test')
expect(database.adapter_name).to eq('test')
end
end
describe '#human_adapter_name' do
context 'when the adapter is PostgreSQL' do
it 'returns PostgreSQL' do
allow(connection).to receive(:config).and_return(adapter: 'postgresql')
allow(database).to receive(:config).and_return(adapter: 'postgresql')
expect(connection.human_adapter_name).to eq('PostgreSQL')
expect(database.human_adapter_name).to eq('PostgreSQL')
end
end
context 'when the adapter is not PostgreSQL' do
it 'returns Unknown' do
allow(connection).to receive(:config).and_return(adapter: 'kittens')
allow(database).to receive(:config).and_return(adapter: 'kittens')
expect(connection.human_adapter_name).to eq('Unknown')
expect(database.human_adapter_name).to eq('Unknown')
end
end
end
......@@ -97,117 +61,117 @@ RSpec.describe Gitlab::Database::Connection do
describe '#postgresql?' do
context 'when using PostgreSQL' do
it 'returns true' do
allow(connection).to receive(:adapter_name).and_return('PostgreSQL')
allow(database).to receive(:adapter_name).and_return('PostgreSQL')
expect(connection.postgresql?).to eq(true)
expect(database.postgresql?).to eq(true)
end
end
context 'when not using PostgreSQL' do
it 'returns false' do
allow(connection).to receive(:adapter_name).and_return('MySQL')
allow(database).to receive(:adapter_name).and_return('MySQL')
expect(connection.postgresql?).to eq(false)
expect(database.postgresql?).to eq(false)
end
end
end
describe '#db_read_only?' do
it 'detects a read-only database' do
allow(connection.scope.connection)
allow(database.model.connection)
.to receive(:execute)
.with('SELECT pg_is_in_recovery()')
.and_return([{ "pg_is_in_recovery" => "t" }])
expect(connection.db_read_only?).to be_truthy
expect(database.db_read_only?).to be_truthy
end
it 'detects a read-only database' do
allow(connection.scope.connection)
allow(database.model.connection)
.to receive(:execute)
.with('SELECT pg_is_in_recovery()')
.and_return([{ "pg_is_in_recovery" => true }])
expect(connection.db_read_only?).to be_truthy
expect(database.db_read_only?).to be_truthy
end
it 'detects a read-write database' do
allow(connection.scope.connection)
allow(database.model.connection)
.to receive(:execute)
.with('SELECT pg_is_in_recovery()')
.and_return([{ "pg_is_in_recovery" => "f" }])
expect(connection.db_read_only?).to be_falsey
expect(database.db_read_only?).to be_falsey
end
it 'detects a read-write database' do
allow(connection.scope.connection)
allow(database.model.connection)
.to receive(:execute)
.with('SELECT pg_is_in_recovery()')
.and_return([{ "pg_is_in_recovery" => false }])
expect(connection.db_read_only?).to be_falsey
expect(database.db_read_only?).to be_falsey
end
end
describe '#db_read_write?' do
it 'detects a read-only database' do
allow(connection.scope.connection)
allow(database.model.connection)
.to receive(:execute)
.with('SELECT pg_is_in_recovery()')
.and_return([{ "pg_is_in_recovery" => "t" }])
expect(connection.db_read_write?).to eq(false)
expect(database.db_read_write?).to eq(false)
end
it 'detects a read-only database' do
allow(connection.scope.connection)
allow(database.model.connection)
.to receive(:execute)
.with('SELECT pg_is_in_recovery()')
.and_return([{ "pg_is_in_recovery" => true }])
expect(connection.db_read_write?).to eq(false)
expect(database.db_read_write?).to eq(false)
end
it 'detects a read-write database' do
allow(connection.scope.connection)
allow(database.model.connection)
.to receive(:execute)
.with('SELECT pg_is_in_recovery()')
.and_return([{ "pg_is_in_recovery" => "f" }])
expect(connection.db_read_write?).to eq(true)
expect(database.db_read_write?).to eq(true)
end
it 'detects a read-write database' do
allow(connection.scope.connection)
allow(database.model.connection)
.to receive(:execute)
.with('SELECT pg_is_in_recovery()')
.and_return([{ "pg_is_in_recovery" => false }])
expect(connection.db_read_write?).to eq(true)
expect(database.db_read_write?).to eq(true)
end
end
describe '#version' do
around do |example|
connection.instance_variable_set(:@version, nil)
database.instance_variable_set(:@version, nil)
example.run
connection.instance_variable_set(:@version, nil)
database.instance_variable_set(:@version, nil)
end
context "on postgresql" do
it "extracts the version number" do
allow(connection)
allow(database)
.to receive(:database_version)
.and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
expect(connection.version).to eq '9.4.4'
expect(database.version).to eq '9.4.4'
end
end
it 'memoizes the result' do
count = ActiveRecord::QueryRecorder
.new { 2.times { connection.version } }
.new { 2.times { database.version } }
.count
expect(count).to eq(1)
......@@ -216,124 +180,31 @@ RSpec.describe Gitlab::Database::Connection do
describe '#postgresql_minimum_supported_version?' do
it 'returns false when using PostgreSQL 10' do
allow(connection).to receive(:version).and_return('10')
allow(database).to receive(:version).and_return('10')
expect(connection.postgresql_minimum_supported_version?).to eq(false)
expect(database.postgresql_minimum_supported_version?).to eq(false)
end
it 'returns false when using PostgreSQL 11' do
allow(connection).to receive(:version).and_return('11')
allow(database).to receive(:version).and_return('11')
expect(connection.postgresql_minimum_supported_version?).to eq(false)
expect(database.postgresql_minimum_supported_version?).to eq(false)
end
it 'returns true when using PostgreSQL 12' do
allow(connection).to receive(:version).and_return('12')
expect(connection.postgresql_minimum_supported_version?).to eq(true)
end
end
describe '#bulk_insert' do
before do
allow(connection).to receive(:connection).and_return(dummy_connection)
allow(dummy_connection).to receive(:quote_column_name, &:itself)
allow(dummy_connection).to receive(:quote, &:itself)
allow(dummy_connection).to receive(:execute)
end
let(:dummy_connection) { double(:connection) }
let(:rows) do
[
{ a: 1, b: 2, c: 3 },
{ c: 6, a: 4, b: 5 }
]
end
it 'does nothing with empty rows' do
expect(dummy_connection).not_to receive(:execute)
connection.bulk_insert('test', [])
end
it 'uses the ordering from the first row' do
expect(dummy_connection).to receive(:execute) do |sql|
expect(sql).to include('(1, 2, 3)')
expect(sql).to include('(4, 5, 6)')
end
connection.bulk_insert('test', rows)
end
it 'quotes column names' do
expect(dummy_connection).to receive(:quote_column_name).with(:a)
expect(dummy_connection).to receive(:quote_column_name).with(:b)
expect(dummy_connection).to receive(:quote_column_name).with(:c)
connection.bulk_insert('test', rows)
end
allow(database).to receive(:version).and_return('12')
it 'quotes values' do
1.upto(6) do |i|
expect(dummy_connection).to receive(:quote).with(i)
end
connection.bulk_insert('test', rows)
end
it 'does not quote values of a column in the disable_quote option' do
[1, 2, 4, 5].each do |i|
expect(dummy_connection).to receive(:quote).with(i)
end
connection.bulk_insert('test', rows, disable_quote: :c)
end
it 'does not quote values of columns in the disable_quote option' do
[2, 5].each do |i|
expect(dummy_connection).to receive(:quote).with(i)
end
connection.bulk_insert('test', rows, disable_quote: [:a, :c])
end
it 'handles non-UTF-8 data' do
expect { connection.bulk_insert('test', [{ a: "\255" }]) }.not_to raise_error
end
context 'when using PostgreSQL' do
it 'allows the returning of the IDs of the inserted rows' do
result = double(:result, values: [['10']])
expect(dummy_connection)
.to receive(:execute)
.with(/RETURNING id/)
.and_return(result)
ids = connection
.bulk_insert('test', [{ number: 10 }], return_ids: true)
expect(ids).to eq([10])
end
it 'allows setting the upsert to do nothing' do
expect(dummy_connection)
.to receive(:execute)
.with(/ON CONFLICT DO NOTHING/)
connection
.bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing)
end
expect(database.postgresql_minimum_supported_version?).to eq(true)
end
end
describe '#cached_column_exists?' do
it 'only retrieves the data from the schema cache' do
database = described_class.new(Project)
queries = ActiveRecord::QueryRecorder.new do
2.times do
expect(connection.cached_column_exists?(:projects, :id)).to be_truthy
expect(connection.cached_column_exists?(:projects, :bogus_column)).to be_falsey
expect(database.cached_column_exists?(:id)).to be_truthy
expect(database.cached_column_exists?(:bogus_column)).to be_falsey
end
end
......@@ -343,10 +214,14 @@ RSpec.describe Gitlab::Database::Connection do
describe '#cached_table_exists?' do
it 'only retrieves the data from the schema cache' do
dummy = Class.new(ActiveRecord::Base) do
self.table_name = 'bogus_table_name'
end
queries = ActiveRecord::QueryRecorder.new do
2.times do
expect(connection.cached_table_exists?(:projects)).to be_truthy
expect(connection.cached_table_exists?(:bogus_table_name)).to be_falsey
expect(described_class.new(Project).cached_table_exists?).to be_truthy
expect(described_class.new(dummy).cached_table_exists?).to be_falsey
end
end
......@@ -354,31 +229,52 @@ RSpec.describe Gitlab::Database::Connection do
end
it 'returns false when database does not exist' do
expect(connection.scope).to receive(:connection) do
database = described_class.new(Project)
expect(database.model).to receive(:connection) do
raise ActiveRecord::NoDatabaseError, 'broken'
end
expect(connection.cached_table_exists?(:projects)).to be(false)
expect(database.cached_table_exists?).to be(false)
end
end
describe '#exists?' do
it 'returns true if the database exists' do
expect(connection.exists?).to be(true)
expect(database.exists?).to be(true)
end
it "returns false if the database doesn't exist" do
expect(connection.scope.connection.schema_cache)
expect(database.model.connection.schema_cache)
.to receive(:database_version)
.and_raise(ActiveRecord::NoDatabaseError)
expect(connection.exists?).to be(false)
expect(database.exists?).to be(false)
end
end
describe '#system_id' do
it 'returns the PostgreSQL system identifier' do
expect(connection.system_id).to be_an_instance_of(Integer)
expect(database.system_id).to be_an_instance_of(Integer)
end
end
describe '#config' do
it 'returns a HashWithIndifferentAccess' do
expect(database.config)
.to be_an_instance_of(HashWithIndifferentAccess)
end
it 'returns a default pool size' do
expect(database.config)
.to include(pool: Gitlab::Database.default_pool_size)
end
it 'does not cache its results' do
a = database.config
b = database.config
expect(a).not_to equal(b)
end
end
end
......@@ -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
# frozen_string_literal: true
require 'spec_helper'
# rubocop: disable Gitlab/BulkInsert
RSpec.describe LegacyBulkInsert do
let(:model) { ApplicationRecord }
describe '#bulk_insert' do
before do
allow(model).to receive(:connection).and_return(dummy_connection)
allow(dummy_connection).to receive(:quote_column_name, &:itself)
allow(dummy_connection).to receive(:quote, &:itself)
allow(dummy_connection).to receive(:execute)
end
let(:dummy_connection) { double(:connection) }
let(:rows) do
[
{ a: 1, b: 2, c: 3 },
{ c: 6, a: 4, b: 5 }
]
end
it 'does nothing with empty rows' do
expect(dummy_connection).not_to receive(:execute)
model.legacy_bulk_insert('test', [])
end
it 'uses the ordering from the first row' do
expect(dummy_connection).to receive(:execute) do |sql|
expect(sql).to include('(1, 2, 3)')
expect(sql).to include('(4, 5, 6)')
end
model.legacy_bulk_insert('test', rows)
end
it 'quotes column names' do
expect(dummy_connection).to receive(:quote_column_name).with(:a)
expect(dummy_connection).to receive(:quote_column_name).with(:b)
expect(dummy_connection).to receive(:quote_column_name).with(:c)
model.legacy_bulk_insert('test', rows)
end
it 'quotes values' do
1.upto(6) do |i|
expect(dummy_connection).to receive(:quote).with(i)
end
model.legacy_bulk_insert('test', rows)
end
it 'does not quote values of a column in the disable_quote option' do
[1, 2, 4, 5].each do |i|
expect(dummy_connection).to receive(:quote).with(i)
end
model.legacy_bulk_insert('test', rows, disable_quote: :c)
end
it 'does not quote values of columns in the disable_quote option' do
[2, 5].each do |i|
expect(dummy_connection).to receive(:quote).with(i)
end
model.legacy_bulk_insert('test', rows, disable_quote: [:a, :c])
end
it 'handles non-UTF-8 data' do
expect { model.legacy_bulk_insert('test', [{ a: "\255" }]) }.not_to raise_error
end
context 'when using PostgreSQL' do
it 'allows the returning of the IDs of the inserted rows' do
result = double(:result, values: [['10']])
expect(dummy_connection)
.to receive(:execute)
.with(/RETURNING id/)
.and_return(result)
ids = model
.legacy_bulk_insert('test', [{ number: 10 }], return_ids: true)
expect(ids).to eq([10])
end
it 'allows setting the upsert to do nothing' do
expect(dummy_connection)
.to receive(:execute)
.with(/ON CONFLICT DO NOTHING/)
model
.legacy_bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing)
end
end
end
end
# rubocop: enable Gitlab/BulkInsert
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Sha256Attribute do
let(:model) { Class.new { include Sha256Attribute } }
let(:model) { Class.new(ApplicationRecord) { include Sha256Attribute } }
before do
columns = [
......
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ShaAttribute do
let(:model) { Class.new { include ShaAttribute } }
let(:model) { Class.new(ApplicationRecord) { include ShaAttribute } }
before do
columns = [
......
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe X509SerialNumberAttribute do
let(:model) { Class.new { include X509SerialNumberAttribute } }
let(:model) { Class.new(ApplicationRecord) { include X509SerialNumberAttribute } }
before do
columns = [
......
......@@ -71,7 +71,7 @@ RSpec.describe MergeRequestDiffCommit do
subject { described_class.create_bulk(merge_request_diff_id, commits) }
it 'inserts the commits into the database en masse' do
expect(Gitlab::Database.main).to receive(:bulk_insert)
expect(ApplicationRecord).to receive(:legacy_bulk_insert)
.with(described_class.table_name, rows)
subject
......@@ -114,7 +114,7 @@ RSpec.describe MergeRequestDiffCommit do
end
it 'uses a sanitized date' do
expect(Gitlab::Database.main).to receive(:bulk_insert)
expect(ApplicationRecord).to receive(:legacy_bulk_insert)
.with(described_class.table_name, rows)
subject
......
......@@ -240,8 +240,8 @@ RSpec.describe MergeRequestDiff do
stub_external_diffs_setting(enabled: true)
expect(diff).not_to receive(:save!)
expect(Gitlab::Database.main)
.to receive(:bulk_insert)
expect(ApplicationRecord)
.to receive(:legacy_bulk_insert)
.with('merge_request_diff_files', anything)
.and_raise(ActiveRecord::Rollback)
......
......@@ -6,17 +6,17 @@ require_relative '../../../../rubocop/cop/gitlab/bulk_insert'
RSpec.describe RuboCop::Cop::Gitlab::BulkInsert do
subject(:cop) { described_class.new }
it 'flags the use of Gitlab::Database.main.bulk_insert' do
it 'flags the use of ApplicationRecord.legacy_bulk_insert' do
expect_offense(<<~SOURCE)
Gitlab::Database.main.bulk_insert('merge_request_diff_files', rows)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `BulkInsertSafe` concern, [...]
ApplicationRecord.legacy_bulk_insert('merge_request_diff_files', rows)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `BulkInsertSafe` concern, [...]
SOURCE
end
it 'flags the use of ::Gitlab::Database.main.bulk_insert' do
it 'flags the use of ::ApplicationRecord.legacy_bulk_insert' do
expect_offense(<<~SOURCE)
::Gitlab::Database.main.bulk_insert('merge_request_diff_files', rows)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `BulkInsertSafe` concern, [...]
::ApplicationRecord.legacy_bulk_insert('merge_request_diff_files', rows)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `BulkInsertSafe` concern, [...]
SOURCE
end
end
......@@ -58,9 +58,9 @@ RSpec.describe Packages::CreateDependencyService do
let_it_be(:rows) { [{ name: 'express', version_pattern: '^4.16.4' }] }
it 'creates dependences and links' do
original_bulk_insert = ::Gitlab::Database.main.method(:bulk_insert)
expect(::Gitlab::Database.main)
.to receive(:bulk_insert) do |table, rows, return_ids: false, disable_quote: [], on_conflict: nil|
original_bulk_insert = ::ApplicationRecord.method(:legacy_bulk_insert)
expect(::ApplicationRecord)
.to receive(:legacy_bulk_insert) do |table, rows, return_ids: false, disable_quote: [], on_conflict: nil|
call_count = table == Packages::Dependency.table_name ? 2 : 1
call_count.times { original_bulk_insert.call(table, rows, return_ids: return_ids, disable_quote: disable_quote, on_conflict: on_conflict) }
end.twice
......
......@@ -50,7 +50,7 @@ RSpec.describe Packages::UpdateTagsService do
it 'is a no op' do
expect(package).not_to receive(:tags)
expect(::Gitlab::Database.main).not_to receive(:bulk_insert)
expect(::ApplicationRecord).not_to receive(:legacy_bulk_insert)
subject
end
......
......@@ -54,7 +54,7 @@ RSpec.describe ResourceEvents::ChangeLabelsService do
let(:removed) { [labels[1]] }
it 'creates all label events in a single query' do
expect(Gitlab::Database.main).to receive(:bulk_insert).once.and_call_original
expect(ApplicationRecord).to receive(:legacy_bulk_insert).once.and_call_original
expect { subject }.to change { resource.resource_label_events.count }.from(0).to(2)
end
end
......
......@@ -238,7 +238,7 @@ RSpec.configure do |config|
# We can't use an `around` hook here because the wrapping transaction
# is not yet opened at the time that is triggered
config.prepend_before do
Gitlab::Database.main.set_open_transactions_baseline
ApplicationRecord.set_open_transactions_baseline
end
config.append_before do
......@@ -246,7 +246,7 @@ RSpec.configure do |config|
end
config.append_after do
Gitlab::Database.main.reset_open_transactions_baseline
ApplicationRecord.reset_open_transactions_baseline
end
config.before do |example|
......
......@@ -11,7 +11,7 @@ RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
context 'when PG version is <12' do
it 'does not add MATERIALIZE keyword' do
allow(Gitlab::Database.main).to receive(:version).and_return('11.1')
allow(ApplicationRecord.database).to receive(:version).and_return('11.1')
expect(query).to include(expected_query_block_without_materialized)
end
......@@ -19,14 +19,14 @@ RSpec.shared_examples 'CTE with MATERIALIZED keyword examples' do
context 'when PG version is >=12' do
it 'adds MATERIALIZE keyword' do
allow(Gitlab::Database.main).to receive(:version).and_return('12.1')
allow(ApplicationRecord.database).to receive(:version).and_return('12.1')
expect(query).to include(expected_query_block_with_materialized)
end
context 'when version is higher than 12' do
it 'adds MATERIALIZE keyword' do
allow(Gitlab::Database.main).to receive(:version).and_return('15.1')
allow(ApplicationRecord.database).to receive(:version).and_return('15.1')
expect(query).to include(expected_query_block_with_materialized)
end
......
......@@ -95,9 +95,9 @@ module TestReportsHelper
<<-EOF.strip_heredoc
junit.framework.AssertionFailedError: expected:&lt;1&gt; but was:&lt;3&gt;
at CalculatorTest.subtractExpression(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/jdk.internal.database.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.database.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.database.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
EOF
end
end
......@@ -201,7 +201,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
describe 'reindex' do
let(:reindex) { double('reindex') }
let(:indexes) { double('indexes') }
let(:databases) { Gitlab::Database.databases }
let(:databases) { Gitlab::Database.database_base_models }
let(:databases_count) { databases.count }
it 'cleans up any leftover indexes' do
......@@ -250,7 +250,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
end
it 'defaults to main database' do
expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(Gitlab::Database.main.scope.connection).and_call_original
expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(ActiveRecord::Base.connection).and_call_original
expect do
run_rake_task('gitlab:db:enqueue_reindexing_action', "[#{index_name}]")
......
......@@ -90,7 +90,7 @@ RSpec.describe 'rake gitlab:storage:*', :silence_stdout do
shared_examples 'wait until database is ready' do
it 'checks if the database is ready once' do
expect(Gitlab::Database.main).to receive(:exists?).once
expect(ApplicationRecord.database).to receive(:exists?).once
run_rake_task(task)
end
......@@ -102,7 +102,7 @@ RSpec.describe 'rake gitlab:storage:*', :silence_stdout do
end
it 'tries for 3 times, polling every 0.1 seconds' do
expect(Gitlab::Database.main).to receive(:exists?).exactly(3).times.and_return(false)
expect(ApplicationRecord.database).to receive(:exists?).exactly(3).times.and_return(false)
run_rake_task(task)
end
......
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