2021-12-11 22:18:48 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
2023-04-23 21:23:45 +05:30
|
|
|
RSpec.describe Gitlab::Database::Reflection, feature_category: :database do
|
2021-12-11 22:18:48 +05:30
|
|
|
let(:database) { described_class.new(ApplicationRecord) }
|
|
|
|
|
|
|
|
describe '#username' do
|
|
|
|
context 'when a username is set' do
|
|
|
|
it 'returns the username' do
|
|
|
|
allow(database).to receive(:config).and_return(username: '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(database).to receive(:config).and_return(username: nil)
|
|
|
|
allow(ENV).to receive(:[]).with('USER').and_return('bob')
|
|
|
|
|
|
|
|
expect(database.username).to eq('bob')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#database_name' do
|
|
|
|
it 'returns the name of the database' do
|
|
|
|
allow(database).to receive(:config).and_return(database: 'test')
|
|
|
|
|
|
|
|
expect(database.database_name).to eq('test')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#adapter_name' do
|
|
|
|
it 'returns the database adapter name' do
|
|
|
|
allow(database).to receive(:config).and_return(adapter: '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(database).to receive(:config).and_return(adapter: 'postgresql')
|
|
|
|
|
|
|
|
expect(database.human_adapter_name).to eq('PostgreSQL')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the adapter is not PostgreSQL' do
|
|
|
|
it 'returns Unknown' do
|
|
|
|
allow(database).to receive(:config).and_return(adapter: 'kittens')
|
|
|
|
|
|
|
|
expect(database.human_adapter_name).to eq('Unknown')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#postgresql?' do
|
|
|
|
context 'when using PostgreSQL' do
|
|
|
|
it 'returns true' do
|
|
|
|
allow(database).to receive(:adapter_name).and_return('PostgreSQL')
|
|
|
|
|
|
|
|
expect(database.postgresql?).to eq(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when not using PostgreSQL' do
|
|
|
|
it 'returns false' do
|
|
|
|
allow(database).to receive(:adapter_name).and_return('MySQL')
|
|
|
|
|
|
|
|
expect(database.postgresql?).to eq(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#db_read_only?' do
|
|
|
|
it 'detects a read-only database' do
|
|
|
|
allow(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.with('SELECT pg_is_in_recovery()')
|
|
|
|
.and_return([{ "pg_is_in_recovery" => "t" }])
|
|
|
|
|
|
|
|
expect(database.db_read_only?).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'detects a read-only database' do
|
|
|
|
allow(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.with('SELECT pg_is_in_recovery()')
|
|
|
|
.and_return([{ "pg_is_in_recovery" => true }])
|
|
|
|
|
|
|
|
expect(database.db_read_only?).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'detects a read-write database' do
|
|
|
|
allow(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.with('SELECT pg_is_in_recovery()')
|
|
|
|
.and_return([{ "pg_is_in_recovery" => "f" }])
|
|
|
|
|
|
|
|
expect(database.db_read_only?).to be_falsey
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'detects a read-write database' do
|
|
|
|
allow(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.with('SELECT pg_is_in_recovery()')
|
|
|
|
.and_return([{ "pg_is_in_recovery" => false }])
|
|
|
|
|
|
|
|
expect(database.db_read_only?).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#db_read_write?' do
|
|
|
|
it 'detects a read-only database' do
|
|
|
|
allow(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.with('SELECT pg_is_in_recovery()')
|
|
|
|
.and_return([{ "pg_is_in_recovery" => "t" }])
|
|
|
|
|
|
|
|
expect(database.db_read_write?).to eq(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'detects a read-only database' do
|
|
|
|
allow(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.with('SELECT pg_is_in_recovery()')
|
|
|
|
.and_return([{ "pg_is_in_recovery" => true }])
|
|
|
|
|
|
|
|
expect(database.db_read_write?).to eq(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'detects a read-write database' do
|
|
|
|
allow(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.with('SELECT pg_is_in_recovery()')
|
|
|
|
.and_return([{ "pg_is_in_recovery" => "f" }])
|
|
|
|
|
|
|
|
expect(database.db_read_write?).to eq(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'detects a read-write database' do
|
|
|
|
allow(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.with('SELECT pg_is_in_recovery()')
|
|
|
|
.and_return([{ "pg_is_in_recovery" => false }])
|
|
|
|
|
|
|
|
expect(database.db_read_write?).to eq(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#version' do
|
|
|
|
around do |example|
|
|
|
|
database.instance_variable_set(:@version, nil)
|
|
|
|
example.run
|
|
|
|
database.instance_variable_set(:@version, nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
context "on postgresql" do
|
|
|
|
it "extracts the version number" do
|
|
|
|
allow(database)
|
|
|
|
.to receive(:database_version)
|
|
|
|
.and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
|
|
|
|
|
|
|
|
expect(database.version).to eq '9.4.4'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'memoizes the result' do
|
|
|
|
count = ActiveRecord::QueryRecorder
|
|
|
|
.new { 2.times { database.version } }
|
|
|
|
.count
|
|
|
|
|
|
|
|
expect(count).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#postgresql_minimum_supported_version?' do
|
|
|
|
it 'returns false when using PostgreSQL 10' do
|
|
|
|
allow(database).to receive(:version).and_return('10')
|
|
|
|
|
|
|
|
expect(database.postgresql_minimum_supported_version?).to eq(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false when using PostgreSQL 11' do
|
|
|
|
allow(database).to receive(:version).and_return('11')
|
|
|
|
|
|
|
|
expect(database.postgresql_minimum_supported_version?).to eq(false)
|
|
|
|
end
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
it 'returns false when using PostgreSQL 12' do
|
2021-12-11 22:18:48 +05:30
|
|
|
allow(database).to receive(:version).and_return('12')
|
|
|
|
|
2023-07-09 08:55:56 +05:30
|
|
|
expect(database.postgresql_minimum_supported_version?).to eq(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns true when using PostgreSQL 13' do
|
|
|
|
allow(database).to receive(:version).and_return('13')
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
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(database.cached_column_exists?(:id)).to be_truthy
|
|
|
|
expect(database.cached_column_exists?(:bogus_column)).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(queries.count).to eq(0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
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(described_class.new(Project).cached_table_exists?).to be_truthy
|
|
|
|
expect(described_class.new(dummy).cached_table_exists?).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(queries.count).to eq(0)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false when database does not exist' do
|
|
|
|
database = described_class.new(Project)
|
|
|
|
|
|
|
|
expect(database.model).to receive(:connection) do
|
|
|
|
raise ActiveRecord::NoDatabaseError, 'broken'
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(database.cached_table_exists?).to be(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#exists?' do
|
|
|
|
it 'returns true if the database exists' do
|
|
|
|
expect(database.exists?).to be(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns false if the database doesn't exist" do
|
|
|
|
expect(database.model.connection.schema_cache)
|
|
|
|
.to receive(:database_version)
|
|
|
|
.and_raise(ActiveRecord::NoDatabaseError)
|
|
|
|
|
|
|
|
expect(database.exists?).to be(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#system_id' do
|
|
|
|
it 'returns the PostgreSQL system identifier' do
|
|
|
|
expect(database.system_id).to be_an_instance_of(Integer)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
describe '#flavor', :delete do
|
|
|
|
let(:result) { [double] }
|
|
|
|
let(:connection) { database.model.connection }
|
|
|
|
|
|
|
|
def stub_statements(statements)
|
|
|
|
statements = Array.wrap(statements)
|
|
|
|
execute = connection.method(:execute)
|
|
|
|
|
|
|
|
allow(connection).to receive(:execute) do |arg|
|
|
|
|
if statements.include?(arg)
|
|
|
|
result
|
|
|
|
else
|
|
|
|
execute.call(arg)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 're-raises exceptions not matching expected messages' do
|
|
|
|
expect(database.model.connection)
|
|
|
|
.to receive(:execute)
|
|
|
|
.and_raise(ActiveRecord::StatementInvalid, 'Something else')
|
|
|
|
|
|
|
|
expect { database.flavor }.to raise_error ActiveRecord::StatementInvalid, /Something else/
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'recognizes Amazon Aurora PostgreSQL' do
|
|
|
|
stub_statements(['SHOW rds.extensions', 'SELECT AURORA_VERSION()'])
|
|
|
|
|
|
|
|
expect(database.flavor).to eq('Amazon Aurora PostgreSQL')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'recognizes PostgreSQL on Amazon RDS' do
|
|
|
|
stub_statements('SHOW rds.extensions')
|
|
|
|
|
|
|
|
expect(database.flavor).to eq('PostgreSQL on Amazon RDS')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'recognizes CloudSQL for PostgreSQL' do
|
|
|
|
stub_statements('SHOW cloudsql.iam_authentication')
|
|
|
|
|
|
|
|
expect(database.flavor).to eq('Cloud SQL for PostgreSQL')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'recognizes Azure Database for PostgreSQL - Flexible Server' do
|
|
|
|
stub_statements(["SELECT datname FROM pg_database WHERE datname = 'azure_maintenance'", 'SHOW azure.extensions'])
|
|
|
|
|
|
|
|
expect(database.flavor).to eq('Azure Database for PostgreSQL - Flexible Server')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'recognizes Azure Database for PostgreSQL - Single Server' do
|
|
|
|
stub_statements("SELECT datname FROM pg_database WHERE datname = 'azure_maintenance'")
|
|
|
|
|
|
|
|
expect(database.flavor).to eq('Azure Database for PostgreSQL - Single Server')
|
|
|
|
end
|
|
|
|
|
2022-11-25 23:54:43 +05:30
|
|
|
it 'recognizes AlloyDB for PostgreSQL' do
|
|
|
|
stub_statements("SELECT name FROM pg_settings WHERE name LIKE 'alloydb%'")
|
|
|
|
|
|
|
|
expect(database.flavor).to eq('AlloyDB for PostgreSQL')
|
|
|
|
end
|
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
it 'returns nil if can not recognize the flavor' do
|
|
|
|
expect(database.flavor).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
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
|