aac1de46c9
This changes various controllers to use the new EventCollection class for retrieving events. This class uses a JOIN LATERAL query on PostgreSQL to retrieve queries in a more efficient way, while falling back to a simpler / less efficient query for MySQL. The EventCollection class also includes a limit on the number of events to display to prevent malicious users from cycling through all events, as doing so could put a lot of pressure on the database. JOIN LATERAL is only supported on PostgreSQL starting with version 9.3.0 and as such this optimisation is only used when using PostgreSQL 9.3 or newer.
259 lines
7.3 KiB
Ruby
259 lines
7.3 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe Gitlab::Database do
|
|
before do
|
|
stub_const('MigrationTest', Class.new { include Gitlab::Database })
|
|
end
|
|
|
|
describe '.config' do
|
|
it 'returns a Hash' do
|
|
expect(described_class.config).to be_an_instance_of(Hash)
|
|
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
|
|
end
|
|
|
|
# These are just simple smoke tests to check if the methods work (regardless
|
|
# of what they may return).
|
|
describe '.mysql?' do
|
|
subject { described_class.mysql? }
|
|
|
|
it { is_expected.to satisfy { |val| val == true || val == false } }
|
|
end
|
|
|
|
describe '.postgresql?' do
|
|
subject { described_class.postgresql? }
|
|
|
|
it { is_expected.to satisfy { |val| val == true || val == false } }
|
|
end
|
|
|
|
describe '.version' do
|
|
context "on mysql" do
|
|
it "extracts the version number" do
|
|
allow(described_class).to receive(:database_version)
|
|
.and_return("5.7.12-standard")
|
|
|
|
expect(described_class.version).to eq '5.7.12-standard'
|
|
end
|
|
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
|
|
end
|
|
|
|
describe '.join_lateral_supported?' do
|
|
it 'returns false when using MySQL' do
|
|
allow(described_class).to receive(:postgresql?).and_return(false)
|
|
|
|
expect(described_class.join_lateral_supported?).to eq(false)
|
|
end
|
|
|
|
it 'returns false when using PostgreSQL 9.2' do
|
|
allow(described_class).to receive(:postgresql?).and_return(true)
|
|
allow(described_class).to receive(:version).and_return('9.2.1')
|
|
|
|
expect(described_class.join_lateral_supported?).to eq(false)
|
|
end
|
|
|
|
it 'returns true when using PostgreSQL 9.3.0 or newer' do
|
|
allow(described_class).to receive(:postgresql?).and_return(true)
|
|
allow(described_class).to receive(:version).and_return('9.3.0')
|
|
|
|
expect(described_class.join_lateral_supported?).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe '.nulls_last_order' do
|
|
context 'when using PostgreSQL' do
|
|
before do
|
|
expect(described_class).to receive(:postgresql?).and_return(true)
|
|
end
|
|
|
|
it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'}
|
|
it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'}
|
|
end
|
|
|
|
context 'when using MySQL' do
|
|
before do
|
|
expect(described_class).to receive(:postgresql?).and_return(false)
|
|
end
|
|
|
|
it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column IS NULL, column ASC'}
|
|
it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC'}
|
|
end
|
|
end
|
|
|
|
describe '.nulls_first_order' do
|
|
context 'when using PostgreSQL' do
|
|
before do
|
|
expect(described_class).to receive(:postgresql?).and_return(true)
|
|
end
|
|
|
|
it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC NULLS FIRST'}
|
|
it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column DESC NULLS FIRST'}
|
|
end
|
|
|
|
context 'when using MySQL' do
|
|
before do
|
|
expect(described_class).to receive(:postgresql?).and_return(false)
|
|
end
|
|
|
|
it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC'}
|
|
it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column IS NULL, column DESC'}
|
|
end
|
|
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.new('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 'handles non-UTF-8 data' do
|
|
expect { described_class.bulk_insert('test', [{ a: "\255" }]) }.not_to raise_error
|
|
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.spec.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.spec.config[:host]).to eq('127.0.0.1')
|
|
ensure
|
|
pool.disconnect!
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#true_value' do
|
|
it 'returns correct value for PostgreSQL' do
|
|
expect(described_class).to receive(:postgresql?).and_return(true)
|
|
|
|
expect(described_class.true_value).to eq "'t'"
|
|
end
|
|
|
|
it 'returns correct value for MySQL' do
|
|
expect(described_class).to receive(:postgresql?).and_return(false)
|
|
|
|
expect(described_class.true_value).to eq 1
|
|
end
|
|
end
|
|
|
|
describe '#false_value' do
|
|
it 'returns correct value for PostgreSQL' do
|
|
expect(described_class).to receive(:postgresql?).and_return(true)
|
|
|
|
expect(described_class.false_value).to eq "'f'"
|
|
end
|
|
|
|
it 'returns correct value for MySQL' do
|
|
expect(described_class).to receive(:postgresql?).and_return(false)
|
|
|
|
expect(described_class.false_value).to eq 0
|
|
end
|
|
end
|
|
end
|