bda30182e0
This adds the keyword argument "return_ids" to Gitlab::Database.bulk_insert. When set to `true` (and PostgreSQL is used) this method will return an Array of the IDs of the inserted rows, otherwise it will return an empty Array.
301 lines
8.4 KiB
Ruby
301 lines
8.4 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
|
|
|
|
context 'when using PostgreSQL' do
|
|
before do
|
|
allow(described_class).to receive(:mysql?).and_return(false)
|
|
end
|
|
|
|
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
|
|
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
|
|
|
|
describe '#sanitize_timestamp' do
|
|
let(:max_timestamp) { Time.at((1 << 31) - 1) }
|
|
|
|
subject { described_class.sanitize_timestamp(timestamp) }
|
|
|
|
context 'with a timestamp smaller than MAX_TIMESTAMP_VALUE' do
|
|
let(:timestamp) { max_timestamp - 10.years }
|
|
|
|
it 'returns the given timestamp' do
|
|
expect(subject).to eq(timestamp)
|
|
end
|
|
end
|
|
|
|
context 'with a timestamp larger than MAX_TIMESTAMP_VALUE' do
|
|
let(:timestamp) { max_timestamp + 1.second }
|
|
|
|
it 'returns MAX_TIMESTAMP_VALUE' do
|
|
expect(subject).to eq(max_timestamp)
|
|
end
|
|
end
|
|
end
|
|
end
|