Commit fac71f1e authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'pb-multi-db-rake-tasks-2' into 'master'

Update db:drop_tables to have multi-db versions

See merge request gitlab-org/gitlab!80327
parents 5eb53602 48c2f3a6
...@@ -4,8 +4,11 @@ module Gitlab ...@@ -4,8 +4,11 @@ module Gitlab
module Database module Database
module EachDatabase module EachDatabase
class << self class << self
def each_database_connection def each_database_connection(only: nil)
Gitlab::Database.database_base_models.each_pair do |connection_name, model| selected_names = Array.wrap(only)
base_models = select_base_models(selected_names)
base_models.each_pair do |connection_name, model|
connection = model.connection connection = model.connection
with_shared_connection(connection, connection_name) do with_shared_connection(connection, connection_name) do
...@@ -28,6 +31,18 @@ module Gitlab ...@@ -28,6 +31,18 @@ module Gitlab
private private
def select_base_models(names)
base_models = Gitlab::Database.database_base_models
return base_models if names.empty?
names.each_with_object(HashWithIndifferentAccess.new) do |name, hash|
raise ArgumentError, "#{name} is not a valid database name" unless base_models.key?(name)
hash[name] = base_models[name]
end
end
def with_shared_model_connections(shared_model, &blk) def with_shared_model_connections(shared_model, &blk)
Gitlab::Database.database_base_models.each_pair do |connection_name, connection_model| Gitlab::Database.database_base_models.each_pair do |connection_name, connection_model|
if shared_model.limit_connection_names if shared_model.limit_connection_names
......
...@@ -4,30 +4,28 @@ databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml ...@@ -4,30 +4,28 @@ databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
namespace :gitlab do namespace :gitlab do
namespace :db do namespace :db do
desc 'GitLab | DB | Manually insert schema migration version' desc 'GitLab | DB | Manually insert schema migration version on all configured databases'
task :mark_migration_complete, [:version] => :environment do |_, args| task :mark_migration_complete, [:version] => :environment do |_, args|
mark_migration_complete(args[:version]) mark_migration_complete(args[:version])
end end
namespace :mark_migration_complete do namespace :mark_migration_complete do
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database|
desc "Gitlab | DB | Manually insert schema migration version on #{name} database" desc "Gitlab | DB | Manually insert schema migration version on #{database} database"
task name, [:version] => :environment do |_, args| task database, [:version] => :environment do |_, args|
mark_migration_complete(args[:version], database: name) mark_migration_complete(args[:version], only_on: database)
end end
end end
end end
def mark_migration_complete(version, database: nil) def mark_migration_complete(version, only_on: nil)
if version.to_i == 0 if version.to_i == 0
puts 'Must give a version argument that is a non-zero integer'.color(:red) puts 'Must give a version argument that is a non-zero integer'.color(:red)
exit 1 exit 1
end end
Gitlab::Database.database_base_models.each do |name, model| Gitlab::Database::EachDatabase.each_database_connection(only: only_on) do |connection, name|
next if database && database.to_s != name connection.execute("INSERT INTO schema_migrations (version) VALUES (#{connection.quote(version)})")
model.connection.execute("INSERT INTO schema_migrations (version) VALUES (#{model.connection.quote(version)})")
puts "Successfully marked '#{version}' as complete on database #{name}".color(:green) puts "Successfully marked '#{version}' as complete on database #{name}".color(:green)
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
...@@ -35,12 +33,23 @@ namespace :gitlab do ...@@ -35,12 +33,23 @@ namespace :gitlab do
end end
end end
desc 'GitLab | DB | Drop all tables' desc 'GitLab | DB | Drop all tables on all configured databases'
task drop_tables: :environment do task drop_tables: :environment do
connection = ActiveRecord::Base.connection drop_tables
end
# In PostgreSQLAdapter, data_sources returns both views and tables, so use namespace :drop_tables do
# #tables instead ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database|
desc "GitLab | DB | Drop all tables on the #{database} database"
task database => :environment do
drop_tables(only_on: database)
end
end
end
def drop_tables(only_on: nil)
Gitlab::Database::EachDatabase.each_database_connection(only: only_on) do |connection, name|
# In PostgreSQLAdapter, data_sources returns both views and tables, so use tables instead
tables = connection.tables tables = connection.tables
# Removes the entry from the array # Removes the entry from the array
...@@ -63,6 +72,7 @@ namespace :gitlab do ...@@ -63,6 +72,7 @@ namespace :gitlab do
connection.execute("DROP SCHEMA IF EXISTS #{connection.quote_table_name(schema)} CASCADE") connection.execute("DROP SCHEMA IF EXISTS #{connection.quote_table_name(schema)} CASCADE")
end end
end end
end
desc 'GitLab | DB | Configures the database by running migrate, or by loading the schema and seeding if needed' desc 'GitLab | DB | Configures the database by running migrate, or by loading the schema and seeding if needed'
task configure: :environment do task configure: :environment do
...@@ -208,8 +218,6 @@ namespace :gitlab do ...@@ -208,8 +218,6 @@ namespace :gitlab do
end end
namespace :reindex do namespace :reindex do
databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database_name| ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database_name|
desc "Reindex #{database_name} database without downtime to eliminate bloat" desc "Reindex #{database_name} database without downtime to eliminate bloat"
task database_name => :environment do task database_name => :environment do
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Database::EachDatabase do RSpec.describe Gitlab::Database::EachDatabase do
describe '.each_database_connection' do describe '.each_database_connection', :add_ci_connection do
before do before do
allow(Gitlab::Database).to receive(:database_base_models) allow(Gitlab::Database).to receive(:database_base_models)
.and_return({ main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access) .and_return({ main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access)
end end
it 'yields each connection after connecting SharedModel', :add_ci_connection do it 'yields each connection after connecting SharedModel' do
expect(Gitlab::Database::SharedModel).to receive(:using_connection) expect(Gitlab::Database::SharedModel).to receive(:using_connection)
.with(ActiveRecord::Base.connection).ordered.and_yield .with(ActiveRecord::Base.connection).ordered.and_yield
...@@ -22,6 +22,42 @@ RSpec.describe Gitlab::Database::EachDatabase do ...@@ -22,6 +22,42 @@ RSpec.describe Gitlab::Database::EachDatabase do
[Ci::ApplicationRecord.connection, 'ci'] [Ci::ApplicationRecord.connection, 'ci']
) )
end end
context 'when only certain databases are selected' do
it 'yields the selected connections after connecting SharedModel' do
expect(Gitlab::Database::SharedModel).to receive(:using_connection)
.with(Ci::ApplicationRecord.connection).ordered.and_yield
expect { |b| described_class.each_database_connection(only: 'ci', &b) }
.to yield_successive_args([Ci::ApplicationRecord.connection, 'ci'])
end
context 'when the selected names are passed as symbols' do
it 'yields the selected connections after connecting SharedModel' do
expect(Gitlab::Database::SharedModel).to receive(:using_connection)
.with(Ci::ApplicationRecord.connection).ordered.and_yield
expect { |b| described_class.each_database_connection(only: :ci, &b) }
.to yield_successive_args([Ci::ApplicationRecord.connection, 'ci'])
end
end
context 'when the selected names are invalid' do
it 'does not yield any connections' do
expect do |b|
described_class.each_database_connection(only: :notvalid, &b)
rescue ArgumentError => e
expect(e.message).to match(/notvalid is not a valid database name/)
end.not_to yield_control
end
it 'raises an error' do
expect do
described_class.each_database_connection(only: :notvalid) {}
end.to raise_error(ArgumentError, /notvalid is not a valid database name/)
end
end
end
end end
describe '.each_model_connection' do describe '.each_model_connection' do
......
...@@ -261,45 +261,78 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do ...@@ -261,45 +261,78 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
end end
describe 'drop_tables' do describe 'drop_tables' do
subject { run_rake_task('gitlab:db:drop_tables') } let(:tables) { %w(one two schema_migrations) }
let(:tables) { %w(one two) }
let(:views) { %w(three four) } let(:views) { %w(three four) }
let(:schemas) { Gitlab::Database::EXTRA_SCHEMAS }
context 'with a single database' do
let(:connection) { ActiveRecord::Base.connection } let(:connection) { ActiveRecord::Base.connection }
before do before do
skip_if_multiple_databases_are_setup
allow(connection).to receive(:execute).and_return(nil) allow(connection).to receive(:execute).and_return(nil)
allow(connection).to receive(:tables).and_return(tables) allow(connection).to receive(:tables).and_return(tables)
allow(connection).to receive(:views).and_return(views) allow(connection).to receive(:views).and_return(views)
end end
it 'drops all tables, except schema_migrations' do it 'drops all objects for the database', :aggregate_failures do
expect(connection).to receive(:execute).with('DROP TABLE IF EXISTS "one" CASCADE') expect_objects_to_be_dropped(connection)
expect(connection).to receive(:execute).with('DROP TABLE IF EXISTS "two" CASCADE')
subject run_rake_task('gitlab:db:drop_tables')
end
end end
it 'drops all views' do context 'with multiple databases', :aggregate_failures do
expect(connection).to receive(:execute).with('DROP VIEW IF EXISTS "three" CASCADE') let(:main_model) { double(:model, connection: double(:connection, tables: tables, views: views)) }
expect(connection).to receive(:execute).with('DROP VIEW IF EXISTS "four" CASCADE') let(:ci_model) { double(:model, connection: double(:connection, tables: tables, views: views)) }
let(:base_models) { { 'main' => main_model, 'ci' => ci_model } }
subject before do
skip_if_multiple_databases_not_setup
allow(Gitlab::Database).to receive(:database_base_models).and_return(base_models)
allow(main_model.connection).to receive(:table_exists?).with('schema_migrations').and_return(true)
allow(ci_model.connection).to receive(:table_exists?).with('schema_migrations').and_return(true)
(tables + views + schemas).each do |name|
allow(main_model.connection).to receive(:quote_table_name).with(name).and_return("\"#{name}\"")
allow(ci_model.connection).to receive(:quote_table_name).with(name).and_return("\"#{name}\"")
end
end end
it 'truncates schema_migrations table' do it 'drops all objects for all databases', :aggregate_failures do
expect(connection).to receive(:execute).with('TRUNCATE schema_migrations') expect_objects_to_be_dropped(main_model.connection)
expect_objects_to_be_dropped(ci_model.connection)
subject run_rake_task('gitlab:db:drop_tables')
end
context 'when the single database task is used' do
it 'drops all objects for the given database', :aggregate_failures do
expect_objects_to_be_dropped(main_model.connection)
expect(ci_model.connection).not_to receive(:execute)
run_rake_task('gitlab:db:drop_tables:main')
end
end
end end
it 'drops extra schemas' do def expect_objects_to_be_dropped(connection)
expect(connection).to receive(:execute).with('DROP TABLE IF EXISTS "one" CASCADE')
expect(connection).to receive(:execute).with('DROP TABLE IF EXISTS "two" CASCADE')
expect(connection).to receive(:execute).with('DROP VIEW IF EXISTS "three" CASCADE')
expect(connection).to receive(:execute).with('DROP VIEW IF EXISTS "four" CASCADE')
expect(connection).to receive(:execute).with('TRUNCATE schema_migrations')
Gitlab::Database::EXTRA_SCHEMAS.each do |schema| Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
expect(connection).to receive(:execute).with("DROP SCHEMA IF EXISTS \"#{schema}\" CASCADE") expect(connection).to receive(:execute).with("DROP SCHEMA IF EXISTS \"#{schema}\" CASCADE")
end end
subject
end end
end 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