Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb')
-rw-r--r--spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb214
1 files changed, 214 insertions, 0 deletions
diff --git a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
new file mode 100644
index 00000000000..636a09e5710
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
@@ -0,0 +1,214 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy do
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:table_name) { :_test_partitioned_test }
+ let(:model) { double('model', table_name: table_name, ignored_columns: %w[partition]) }
+ let(:next_partition_if) { double('next_partition_if') }
+ let(:detach_partition_if) { double('detach_partition_if') }
+
+ subject(:strategy) do
+ described_class.new(model, :partition,
+ next_partition_if: next_partition_if,
+ detach_partition_if: detach_partition_if)
+ end
+
+ before do
+ connection.execute(<<~SQL)
+ create table #{table_name}
+ (
+ id serial not null,
+ partition bigint not null default 2,
+ created_at timestamptz not null,
+ primary key (id, partition)
+ )
+ partition by list(partition);
+
+ create table #{table_name}_1
+ partition of #{table_name} for values in (1);
+
+ create table #{table_name}_2
+ partition of #{table_name} for values in (2);
+ SQL
+ end
+
+ describe '#current_partitions' do
+ it 'detects both partitions' do
+ expect(strategy.current_partitions).to eq([
+ Gitlab::Database::Partitioning::SingleNumericListPartition.new(table_name, 1, partition_name: '_test_partitioned_test_1'),
+ Gitlab::Database::Partitioning::SingleNumericListPartition.new(table_name, 2, partition_name: '_test_partitioned_test_2')
+ ])
+ end
+ end
+
+ describe '#active_partition' do
+ it 'is the partition with the largest value' do
+ expect(strategy.active_partition.value).to eq(2)
+ end
+ end
+
+ describe '#missing_partitions' do
+ context 'when next_partition_if returns true' do
+ let(:next_partition_if) { proc { true } }
+
+ it 'is a partition definition for the next partition in the series' do
+ extra = strategy.missing_partitions
+
+ expect(extra.length).to eq(1)
+ expect(extra.first.value).to eq(3)
+ end
+ end
+
+ context 'when next_partition_if returns false' do
+ let(:next_partition_if) { proc { false } }
+
+ it 'is empty' do
+ expect(strategy.missing_partitions).to be_empty
+ end
+ end
+
+ context 'when there are no partitions for the table' do
+ it 'returns a partition for value 1' do
+ connection.execute("drop table #{table_name}_1; drop table #{table_name}_2;")
+
+ missing_partitions = strategy.missing_partitions
+
+ expect(missing_partitions.size).to eq(1)
+ missing_partition = missing_partitions.first
+
+ expect(missing_partition.value).to eq(1)
+ end
+ end
+ end
+
+ describe '#extra_partitions' do
+ before do
+ (3..10).each do |i|
+ connection.execute("CREATE TABLE #{table_name}_#{i} PARTITION OF #{table_name} FOR VALUES IN (#{i})")
+ end
+ end
+
+ context 'when some partitions are true for detach_partition_if' do
+ let(:detach_partition_if) { ->(p) { p != 5 } }
+
+ it 'is the leading set of partitions before that value' do
+ expect(strategy.extra_partitions.map(&:value)).to contain_exactly(1, 2, 3, 4)
+ end
+ end
+
+ context 'when all partitions are true for detach_partition_if' do
+ let(:detach_partition_if) { proc { true } }
+
+ it 'is all but the most recent partition', :aggregate_failures do
+ expect(strategy.extra_partitions.map(&:value)).to contain_exactly(1, 2, 3, 4, 5, 6, 7, 8, 9)
+
+ expect(strategy.current_partitions.map(&:value).max).to eq(10)
+ end
+ end
+ end
+
+ describe '#initial_partition' do
+ it 'starts with the value 1', :aggregate_failures do
+ initial_partition = strategy.initial_partition
+ expect(initial_partition.value).to eq(1)
+ expect(initial_partition.table).to eq(strategy.table_name)
+ expect(initial_partition.partition_name).to eq("#{strategy.table_name}_1")
+ end
+ end
+
+ describe '#next_partition' do
+ it 'is one after the active partition', :aggregate_failures do
+ expect(strategy).to receive(:active_partition).and_return(double(value: 5))
+ next_partition = strategy.next_partition
+
+ expect(next_partition.value).to eq(6)
+ expect(next_partition.table).to eq(strategy.table_name)
+ expect(next_partition.partition_name).to eq("#{strategy.table_name}_6")
+ end
+ end
+
+ describe '#ensure_partitioning_column_ignored!' do
+ it 'raises when the column is not ignored' do
+ expect do
+ Class.new(ApplicationRecord) do
+ include PartitionedTable
+
+ partitioned_by :partition, strategy: :sliding_list,
+ next_partition_if: proc { false },
+ detach_partition_if: proc { false }
+ end
+ end.to raise_error(/ignored_columns/)
+ end
+
+ it 'does not raise when the column is ignored' do
+ expect do
+ Class.new(ApplicationRecord) do
+ include PartitionedTable
+
+ self.ignored_columns = [:partition]
+
+ partitioned_by :partition, strategy: :sliding_list,
+ next_partition_if: proc { false },
+ detach_partition_if: proc { false }
+ end
+ end.not_to raise_error
+ end
+ end
+ context 'redirecting inserts as the active partition changes' do
+ let(:model) do
+ Class.new(ApplicationRecord) do
+ include PartitionedTable
+
+ self.table_name = '_test_partitioned_test'
+ self.primary_key = :id
+
+ self.ignored_columns = %w[partition]
+
+ # method().call cannot be detected by rspec, so we add a layer of indirection here
+ def self.next_partition_if_wrapper(...)
+ next_partition?(...)
+ end
+
+ def self.detach_partition_if_wrapper(...)
+ detach_partition?(...)
+ end
+ partitioned_by :partition, strategy: :sliding_list,
+ next_partition_if: method(:next_partition_if_wrapper),
+ detach_partition_if: method(:detach_partition_if_wrapper)
+
+ def self.next_partition?(current_partition)
+ end
+
+ def self.detach_partition?(partition)
+ end
+ end
+ end
+
+ it 'redirects to the new partition', :aggregate_failures do
+ partition_2_model = model.create! # Goes in partition 2
+
+ allow(model).to receive(:next_partition?) do
+ model.partitioning_strategy.active_partition.value < 3
+ end
+
+ allow(model).to receive(:detach_partition?).and_return(false)
+
+ Gitlab::Database::Partitioning::PartitionManager.new(model).sync_partitions
+
+ partition_3_model = model.create!
+
+ # Rails doesn't pick up on database default changes, so we need to reload
+ # We also want to grab the partition column to verify what it was set to.
+ # In normal operation we make rails ignore it so that we can use a changing default
+ # So we force select * to load it
+ all_columns = model.select(model.arel_table[Arel.star])
+ partition_2_model = all_columns.find(partition_2_model.id)
+ partition_3_model = all_columns.find(partition_3_model.id)
+
+ expect(partition_2_model.partition).to eq(2)
+ expect(partition_3_model.partition).to eq(3)
+ end
+ end
+end