diff options
Diffstat (limited to 'spec/lib/gitlab/database_spec.rb')
-rw-r--r-- | spec/lib/gitlab/database_spec.rb | 416 |
1 files changed, 22 insertions, 394 deletions
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index a834e41c019..c67b5af5e3c 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -15,32 +15,6 @@ RSpec.describe Gitlab::Database do end end - describe '.default_pool_size' do - before do - allow(Gitlab::Runtime).to receive(:max_threads).and_return(7) - end - - it 'returns the max thread size plus a fixed headroom of 10' do - expect(described_class.default_pool_size).to eq(17) - end - - it 'returns the max thread size plus a DB_POOL_HEADROOM if this env var is present' do - stub_env('DB_POOL_HEADROOM', '7') - - expect(described_class.default_pool_size).to eq(14) - end - end - - describe '.config' do - it 'returns a HashWithIndifferentAccess' do - expect(described_class.config).to be_an_instance_of(HashWithIndifferentAccess) - end - - it 'returns a default pool size' do - expect(described_class.config).to include(pool: described_class.default_pool_size) - end - end - describe '.has_config?' do context 'two tier database config' do before do @@ -114,108 +88,11 @@ RSpec.describe Gitlab::Database do end end - describe '.adapter_name' do - it 'returns the name of the adapter' do - expect(described_class.adapter_name).to be_an_instance_of(String) - end - - it 'returns Unknown when using anything else' do - allow(described_class).to receive(:postgresql?).and_return(false) - - expect(described_class.human_adapter_name).to eq('Unknown') - end - end - - describe '.human_adapter_name' do - it 'returns PostgreSQL when using PostgreSQL' do - expect(described_class.human_adapter_name).to eq('PostgreSQL') - end - end - - describe '.system_id' do - it 'returns the PostgreSQL system identifier' do - expect(described_class.system_id).to be_an_instance_of(Integer) - end - end - - describe '.disable_prepared_statements' do - around do |example| - original_config = ::Gitlab::Database.config - - example.run - - ActiveRecord::Base.establish_connection(original_config) - end - - it 'disables prepared statements' do - ActiveRecord::Base.establish_connection(::Gitlab::Database.config.merge(prepared_statements: true)) - expect(ActiveRecord::Base.connection.prepared_statements).to eq(true) - - expect(ActiveRecord::Base).to receive(:establish_connection) - .with(a_hash_including({ 'prepared_statements' => false })).and_call_original - - described_class.disable_prepared_statements - - expect(ActiveRecord::Base.connection.prepared_statements).to eq(false) - end - end - - describe '.postgresql?' do - subject { described_class.postgresql? } - - it { is_expected.to satisfy { |val| val == true || val == false } } - end - - describe '.version' do - around do |example| - described_class.instance_variable_set(:@version, nil) - example.run - described_class.instance_variable_set(:@version, nil) - end - - context "on postgresql" do - it "extracts the version number" do - allow(described_class).to receive(:database_version) - .and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0") - - expect(described_class.version).to eq '9.4.4' - end - end - - it 'memoizes the result' do - count = ActiveRecord::QueryRecorder - .new { 2.times { described_class.version } } - .count - - expect(count).to eq(1) - end - end - - describe '.postgresql_minimum_supported_version?' do - it 'returns false when using PostgreSQL 10' do - allow(described_class).to receive(:version).and_return('10') - - expect(described_class.postgresql_minimum_supported_version?).to eq(false) - end - - it 'returns false when using PostgreSQL 11' do - allow(described_class).to receive(:version).and_return('11') - - expect(described_class.postgresql_minimum_supported_version?).to eq(false) - end - - it 'returns true when using PostgreSQL 12' do - allow(described_class).to receive(:version).and_return('12') - - expect(described_class.postgresql_minimum_supported_version?).to eq(true) - end - end - describe '.check_postgres_version_and_print_warning' do subject { described_class.check_postgres_version_and_print_warning } it 'prints a warning if not compliant with minimum postgres version' do - allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(false) + allow(described_class.main).to receive(:postgresql_minimum_supported_version?).and_return(false) expect(Kernel).to receive(:warn).with(/You are using PostgreSQL/) @@ -223,7 +100,7 @@ RSpec.describe Gitlab::Database do end it 'doesnt print a warning if compliant with minimum postgres version' do - allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(true) + allow(described_class.main).to receive(:postgresql_minimum_supported_version?).and_return(true) expect(Kernel).not_to receive(:warn).with(/You are using PostgreSQL/) @@ -231,7 +108,7 @@ RSpec.describe Gitlab::Database do end it 'doesnt print a warning in Rails runner environment' do - allow(described_class).to receive(:postgresql_minimum_supported_version?).and_return(false) + allow(described_class.main).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/) @@ -240,13 +117,13 @@ RSpec.describe Gitlab::Database do end it 'ignores ActiveRecord errors' do - allow(described_class).to receive(:postgresql_minimum_supported_version?).and_raise(ActiveRecord::ActiveRecordError) + allow(described_class.main).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).to receive(:postgresql_minimum_supported_version?).and_raise(PG::Error) + allow(described_class.main).to receive(:postgresql_minimum_supported_version?).and_raise(PG::Error) expect { subject }.not_to raise_error end @@ -262,244 +139,19 @@ RSpec.describe Gitlab::Database do it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column DESC NULLS FIRST'} end - describe '.with_connection_pool' do - it 'creates a new connection pool and disconnect it after used' do - closed_pool = nil - - described_class.with_connection_pool(1) do |pool| - pool.with_connection do |connection| - connection.execute('SELECT 1 AS value') - end - - expect(pool).to be_connected - - closed_pool = pool - end - - expect(closed_pool).not_to be_connected - end - - it 'disconnects the pool even an exception was raised' do - error = Class.new(RuntimeError) - closed_pool = nil - - begin - described_class.with_connection_pool(1) do |pool| - pool.with_connection do |connection| - connection.execute('SELECT 1 AS value') - end - - closed_pool = pool - - raise error, 'boom' - end - rescue error - end - - expect(closed_pool).not_to be_connected - end - end - - describe '.bulk_insert' do - before do - allow(described_class).to receive(:connection).and_return(connection) - allow(connection).to receive(:quote_column_name, &:itself) - allow(connection).to receive(:quote, &:itself) - allow(connection).to receive(:execute) - end - - let(: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(connection).not_to receive(:execute) - - described_class.bulk_insert('test', []) - end - - it 'uses the ordering from the first row' do - expect(connection).to receive(:execute) do |sql| - expect(sql).to include('(1, 2, 3)') - expect(sql).to include('(4, 5, 6)') - end - - described_class.bulk_insert('test', rows) - end - - it 'quotes column names' do - expect(connection).to receive(:quote_column_name).with(:a) - expect(connection).to receive(:quote_column_name).with(:b) - expect(connection).to receive(:quote_column_name).with(:c) - - described_class.bulk_insert('test', rows) - end - - it 'quotes values' do - 1.upto(6) do |i| - expect(connection).to receive(:quote).with(i) - end - - described_class.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(connection).to receive(:quote).with(i) - end - - described_class.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(connection).to receive(:quote).with(i) - end - - described_class.bulk_insert('test', rows, disable_quote: [:a, :c]) - end - - it 'handles non-UTF-8 data' do - expect { described_class.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(connection) - .to receive(:execute) - .with(/RETURNING id/) - .and_return(result) - - ids = described_class - .bulk_insert('test', [{ number: 10 }], return_ids: true) - - expect(ids).to eq([10]) - end - - it 'allows setting the upsert to do nothing' do - expect(connection) - .to receive(:execute) - .with(/ON CONFLICT DO NOTHING/) - - described_class - .bulk_insert('test', [{ number: 10 }], on_conflict: :do_nothing) - end - end - end - - describe '.create_connection_pool' do - it 'creates a new connection pool with specific pool size' do - pool = described_class.create_connection_pool(5) - - begin - expect(pool) - .to be_kind_of(ActiveRecord::ConnectionAdapters::ConnectionPool) - - expect(pool.db_config.pool).to eq(5) - ensure - pool.disconnect! - end - end - - it 'allows setting of a custom hostname' do - pool = described_class.create_connection_pool(5, '127.0.0.1') - - begin - expect(pool.db_config.host).to eq('127.0.0.1') - ensure - pool.disconnect! - end - end - - it 'allows setting of a custom hostname and port' do - pool = described_class.create_connection_pool(5, '127.0.0.1', 5432) - - begin - expect(pool.db_config.host).to eq('127.0.0.1') - expect(pool.db_config.configuration_hash[:port]).to eq(5432) - ensure - pool.disconnect! - end - end - end - - describe '.cached_column_exists?' do - it 'only retrieves data once' do - expect(ActiveRecord::Base.connection).to receive(:columns).once.and_call_original - - 2.times do - expect(described_class.cached_column_exists?(:projects, :id)).to be_truthy - expect(described_class.cached_column_exists?(:projects, :bogus_column)).to be_falsey - end - end - end - - describe '.cached_table_exists?' do - it 'only retrieves data once per table' do - expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original - expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original - - 2.times do - expect(described_class.cached_table_exists?(:projects)).to be_truthy - expect(described_class.cached_table_exists?(:bogus_table_name)).to be_falsey - end - end - - it 'returns false when database does not exist' do - expect(ActiveRecord::Base).to receive(:connection) { raise ActiveRecord::NoDatabaseError, 'broken' } - - expect(described_class.cached_table_exists?(:projects)).to be(false) - end - end - - describe '.exists?' do - it 'returns true if `ActiveRecord::Base.connection` succeeds' do - expect(ActiveRecord::Base).to receive(:connection) - - expect(described_class.exists?).to be(true) - end - - it 'returns false if `ActiveRecord::Base.connection` fails' do - expect(ActiveRecord::Base).to receive(:connection) { raise ActiveRecord::NoDatabaseError, 'broken' } - - expect(described_class.exists?).to be(false) - end - end - - describe '.get_write_location' do - it 'returns a string' do + describe '.db_config_name' do + it 'returns the db_config name for the connection' do connection = ActiveRecord::Base.connection - expect(described_class.get_write_location(connection)).to be_a(String) - end - - it 'returns nil if there are no results' do - connection = double(select_all: []) - - expect(described_class.get_write_location(connection)).to be_nil - end - end - - describe '.dbname' do - it 'returns the dbname for the connection' do - connection = ActiveRecord::Base.connection - - expect(described_class.dbname(connection)).to be_a(String) - expect(described_class.dbname(connection)).to eq(connection.pool.db_config.database) + expect(described_class.db_config_name(connection)).to be_a(String) + expect(described_class.db_config_name(connection)).to eq(connection.pool.db_config.name) end context 'when the pool is a NullPool' do it 'returns unknown' do connection = double(:active_record_connection, pool: ActiveRecord::ConnectionAdapters::NullPool.new) - expect(described_class.dbname(connection)).to eq('unknown') + expect(described_class.db_config_name(connection)).to eq('unknown') end end end @@ -516,42 +168,6 @@ RSpec.describe Gitlab::Database do end end - describe '.read_only?' do - it 'returns false' do - expect(described_class.read_only?).to be_falsey - end - end - - describe '.db_read_only?' do - before do - allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original - end - - it 'detects a read-only database' do - allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "t" }]) - - expect(described_class.db_read_only?).to be_truthy - end - - it 'detects a read-only database' do - allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }]) - - expect(described_class.db_read_only?).to be_truthy - end - - it 'detects a read-write database' do - allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }]) - - expect(described_class.db_read_only?).to be_falsey - end - - it 'detects a read-write database' do - allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }]) - - expect(described_class.db_read_only?).to be_falsey - end - end - describe '#sanitize_timestamp' do let(:max_timestamp) { Time.at((1 << 31) - 1) } @@ -574,6 +190,18 @@ RSpec.describe Gitlab::Database do end end + describe '.read_only?' do + it 'returns false' do + expect(described_class.read_only?).to eq(false) + end + end + + describe '.read_write' do + it 'returns true' do + expect(described_class.read_write?).to eq(true) + end + end + describe 'ActiveRecordBaseTransactionMetrics' do def subscribe_events events = [] |