diff --git a/lib/redis_counters/clusterize_and_partitionize.rb b/lib/redis_counters/clusterize_and_partitionize.rb index 6a69da8..a243b0d 100644 --- a/lib/redis_counters/clusterize_and_partitionize.rb +++ b/lib/redis_counters/clusterize_and_partitionize.rb @@ -72,6 +72,25 @@ def delete_partitions!(params = {}) end end + # Public: Транзакционно удаляет все данные счетчика в кластере. + # Если кластеризация не используется, то удаляет все данные. + # + # cluster - Hash - хеш параметров, определяющих кластер. + # Опционально, если кластеризация не используется. + # + # Если передан блок, то вызывает блок, после удаления всех данных, в транзакции. + # + # Returns Nothing. + # + def delete_all!(cluster = {}) + parts = partitions(cluster) + + transaction do + delete_all_direct!(cluster, redis, parts) + yield if block_given? + end + end + # Public: Нетранзакционно удаляет данные конкретной конечной партиции. # # params - Hash - хеш параметров, определяющий кластер и листовую партицию. @@ -91,6 +110,22 @@ def delete_partition_direct!(params = {}, write_session = redis) write_session.del(key) end + # Public: Нетранзакционно удаляет все данные счетчика в кластере. + # Если кластеризация не используется, то удаляет все данные. + # + # cluster - Hash - хеш параметров, определяющих кластер. + # write_session - Redis - соединение с Redis, в рамках которого + # будет производится удаление (опционально). + # По умолчанию - основное соединение счетчика. + # + # Returns Nothing. + # + def delete_all_direct!(cluster, write_session = redis, parts = partitions(cluster)) + parts.each do |partition| + delete_partition_direct!(cluster.merge(partition), write_session) + end + end + protected def key(partition = partition_params, cluster = cluster_params) @@ -121,7 +156,7 @@ def use_partitions? # Protected: Возвращает массив листовых партиций в виде ключей. # - # params - Hash - хеш параметров, определяющий кластер и партицию. + # params - Hash - хеш параметров, определяющий кластер и партицию. # # Если кластер не указан и нет кластеризации в счетчике, то возвращает все партиции. # Партиция может быть не задана, тогда будут возвращены все партиции кластера (все партиции, если нет кластеризации). diff --git a/lib/redis_counters/unique_values_lists/fast.rb b/lib/redis_counters/unique_values_lists/fast.rb index 55a8401..c5ac3d3 100644 --- a/lib/redis_counters/unique_values_lists/fast.rb +++ b/lib/redis_counters/unique_values_lists/fast.rb @@ -9,23 +9,41 @@ module UniqueValuesLists # Особенности: # * 2-х кратный расход памяти в случае использования партиций; # * Не ведет список партиций; - # * Не транзакционен. + # * Не транзакционен; # * Методы delete_partitions! и delete_partition_direct!, удаляют только дублирующие партиции, # но не удаляют данные из основной партиции. # Для удаления основной партиции необходимо вызвать delete_main_partition! + # или воспользоваться методами delete_all! или delete_all_direct!, + # для удаления всех партиций кластера включая основную. class Fast < UniqueValuesLists::Base + # Public: Нетранзакционно удаляет все данные счетчика в кластере, включая основную партицию. + # Если кластеризация не используется, то удаляет все данные. + # + # cluster - Hash - хеш параметров, определяющих кластер. + # write_session - Redis - соединение с Redis, в рамках которого + # будет производится удаление (опционально). + # По умолчанию - основное соединение счетчика. + # + # Returns Nothing. + # + def delete_all_direct!(cluster, write_session = redis, parts = partitions(cluster)) + super(cluster, write_session, parts) + delete_main_partition!(cluster, write_session) + end + # Public: Удаляет основную партицию. # # cluster - Hash - хеш параметров, определяющих кластер. + # Опционально, если кластеризация не используется. # write_session - Redis - соединение с Redis, в рамках которого # будет производится удаление (опционально). # По умолчанию - основное соединение счетчика. # # Returns Nothing. # - def delete_main_partition!(cluster, write_session = redis) + def delete_main_partition!(cluster = {}, write_session = redis) cluster = ::RedisCounters::Cluster.new(self, cluster).params key = key([], cluster) write_session.del(key) diff --git a/spec/redis_counters/hash_counter_spec.rb b/spec/redis_counters/hash_counter_spec.rb index 9ae7a2f..c2c5573 100644 --- a/spec/redis_counters/hash_counter_spec.rb +++ b/spec/redis_counters/hash_counter_spec.rb @@ -354,6 +354,14 @@ before { counter.process(partition_2) } before { counter.process(partition_3) } + context '#delete_all!' do + before { counter.delete_all! } + + it { expect(counter.partitions).to eq [] } + it { expect(counter.data).to eq [] } + it { expect(redis.keys).to eq [] } + end + context '#delete_partitions!' do context 'when no params given' do before { counter.delete_partitions! } diff --git a/spec/support/unique_values_lists.rb b/spec/support/unique_values_lists.rb index dd37afa..74bc9bd 100644 --- a/spec/support/unique_values_lists.rb +++ b/spec/support/unique_values_lists.rb @@ -526,4 +526,114 @@ end end end + + context '#delete_all!' do + let(:cluster1_subcluster1) { {:cluster => :cluster1, :subcluster => :subcluster1} } + let(:cluster1_subcluster2) { {:cluster => :cluster1, :subcluster => :subcluster2} } + let(:cluster1_subcluster3) { {:cluster => :cluster1, :subcluster => :subcluster} } + let(:cluster2_subcluster1) { {:cluster => :cluster2, :subcluster => :subcluster1} } + + let(:part1_subpart1) { {:part => 'part1', :subpart => 'subpart1'}.with_indifferent_access } + let(:part1_subpart2) { {:part => 'part1', :subpart => 'subpart2'}.with_indifferent_access } + let(:part2_subpart1) { {'part' => 'part2', :subpart => 'subpart1'}.with_indifferent_access } + + context 'when cluster and partition keys given' do + let(:options) { { + :counter_name => :test_counter, + :value_keys => [:param0, :param1], + :cluster_keys => [:cluster, :subcluster], + :partition_keys => [:part, :subpart] + } } + + # 2 разных знач в одном кластере и партиции + before { values.times { counter.add(:param0 => 1, :param1 => 2, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart1) } } + before { values.times { counter.add(:param0 => 1, :param1 => 3, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart1) } } + # дубль знач в другой партиции + before { values.times { counter.add(:param0 => 1, :param1 => 2, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart3) } } + # дубль знач в другом кластере + before { values.times { counter.add(:param0 => 1, :param1 => 2, :cluster => :cluster1, :subcluster => :subcluster3, :part => :part1, :subpart => :subpart1) } } + # новое значение в новой подпартиции + before { values.times { counter.add(:param0 => 3, :param1 => 4, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart2) } } + # новое значение в новой партиции + before { values.times { counter.add(:param0 => 4, :param1 => 5, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part2, :subpart => :subpart1) } } + # новое значение в новом кластере + before { values.times { counter.add(:param0 => 5, :param1 => 6, :cluster => :cluster2, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart1) } } + # новое значение в новом подкластере + before { values.times { counter.add(:param0 => 6, :param1 => 7, :cluster => :cluster1, :subcluster => :subcluster2, :part => :part1, :subpart => :subpart1) } } + + context 'when no cluster given' do + it { expect { counter.delete_all! }.to raise_error ArgumentError } + end + + context 'when no leaf cluster given' do + it { expect { counter.delete_all!(:cluster => :cluster1) }.to raise_error KeyError } + end + + context 'when unknown cluster given' do + before { counter.delete_all!(:cluster => :unknown_cluster, :subcluster => :subcluster) } + + it { expect(counter.partitions(cluster1_subcluster1)).to have(3).partitions } + end + + context 'when unknown params given' do + it { expect { counter.delete_all!(:cluster1 => :cluster1) }.to raise_error KeyError } + end + + context 'when no partition given' do + before { counter.delete_all!(cluster1_subcluster1) } + + it { expect(counter.data(cluster1_subcluster1)).to have(0).rows } + it { expect(counter.data(cluster2_subcluster1)).to have(1).rows } + it { expect(counter.data(cluster1_subcluster2)).to have(1).rows } + it { expect(counter.data(cluster2_subcluster1)).to include ({'param0' => '5', 'param1' => '6'}) } + it { expect(counter.data(cluster1_subcluster2)).to include ({'param0' => '6', 'param1' => '7'}) } + + it { expect(counter.partitions(cluster1_subcluster1)).to have(0).partitions } + it { expect(counter.partitions(cluster2_subcluster1)).to have(1).partitions } + it { expect(counter.partitions(cluster2_subcluster1)).to have(1).partitions } + end + end + + context 'when cluster not given and partition keys given' do + let(:options) { { + :counter_name => :test_counter, + :value_keys => [:param0, :param1], + :partition_keys => [:part, :subpart] + } } + + # 2 разных знач в одном кластере и партиции + before { values.times { counter.add(:param0 => 1, :param1 => 2, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart1) } } + before { values.times { counter.add(:param0 => 1, :param1 => 3, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart1) } } + # дубль знач в другой партиции + before { values.times { counter.add(:param0 => 1, :param1 => 2, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart3) } } + # дубль знач в другом кластере + before { values.times { counter.add(:param0 => 1, :param1 => 2, :cluster => :cluster1, :subcluster => :subcluster3, :part => :part1, :subpart => :subpart1) } } + # новое значение в новой подпартиции + before { values.times { counter.add(:param0 => 3, :param1 => 4, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart2) } } + # новое значение в новой партиции + before { values.times { counter.add(:param0 => 4, :param1 => 5, :cluster => :cluster1, :subcluster => :subcluster1, :part => :part2, :subpart => :subpart1) } } + # новое значение в новом кластере + before { values.times { counter.add(:param0 => 5, :param1 => 6, :cluster => :cluster2, :subcluster => :subcluster1, :part => :part1, :subpart => :subpart1) } } + # новое значение в новом подкластере + before { values.times { counter.add(:param0 => 6, :param1 => 7, :cluster => :cluster1, :subcluster => :subcluster2, :part => :part1, :subpart => :subpart1) } } + + context 'when no cluster given' do + before { counter.delete_all! } + + it { expect(counter.partitions).to have(0).partitions } + end + + context 'when unknown cluster given' do + before { counter.delete_all!(:cluster => :unknown_cluster, :subcluster => :subcluster) } + + it { expect(counter.partitions).to have(0).partitions } + end + + context 'when unknown params given' do + before { counter.delete_all!(:cluster1 => :cluster1) } + + it { expect(counter.partitions).to have(0).partitions } + end + end + end end \ No newline at end of file