//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
//

#include "table/block_based/filter_block_reader_common.h"

#include "block_cache.h"
#include "monitoring/perf_context_imp.h"
#include "table/block_based/block_based_table_reader.h"
#include "table/block_based/parsed_full_filter_block.h"

namespace ROCKSDB_NAMESPACE {

template <typename TBlocklike>
Status FilterBlockReaderCommon<TBlocklike>::ReadFilterBlock(
    const BlockBasedTable* table, FilePrefetchBuffer* prefetch_buffer,
    const ReadOptions& read_options, bool use_cache, GetContext* get_context,
    BlockCacheLookupContext* lookup_context,
    CachableEntry<TBlocklike>* filter_block) {
  PERF_TIMER_GUARD(read_filter_block_nanos);

  assert(table);
  assert(filter_block);
  assert(filter_block->IsEmpty());

  const BlockBasedTable::Rep* const rep = table->get_rep();
  assert(rep);

  const Status s = table->RetrieveBlock(
      prefetch_buffer, read_options, rep->filter_handle,
      UncompressionDict::GetEmptyDict(), filter_block, get_context,
      lookup_context,
      /* for_compaction */ false, use_cache,
      /* async_read */ false, /* use_block_cache_for_lookup */ true);

  return s;
}

template <typename TBlocklike>
const SliceTransform*
FilterBlockReaderCommon<TBlocklike>::table_prefix_extractor() const {
  assert(table_);

  const BlockBasedTable::Rep* const rep = table_->get_rep();
  assert(rep);

  return rep->prefix_filtering ? rep->table_prefix_extractor.get() : nullptr;
}

template <typename TBlocklike>
bool FilterBlockReaderCommon<TBlocklike>::whole_key_filtering() const {
  assert(table_);
  assert(table_->get_rep());

  return table_->get_rep()->whole_key_filtering;
}

template <typename TBlocklike>
bool FilterBlockReaderCommon<TBlocklike>::cache_filter_blocks() const {
  assert(table_);
  assert(table_->get_rep());

  return table_->get_rep()->table_options.cache_index_and_filter_blocks;
}

template <typename TBlocklike>
Status FilterBlockReaderCommon<TBlocklike>::GetOrReadFilterBlock(
    bool no_io, GetContext* get_context,
    BlockCacheLookupContext* lookup_context,
    CachableEntry<TBlocklike>* filter_block,
    const ReadOptions& read_options) const {
  assert(filter_block);

  if (!filter_block_.IsEmpty()) {
    filter_block->SetUnownedValue(filter_block_.GetValue());
    return Status::OK();
  }

  ReadOptions ro = read_options;
  if (no_io) {
    ro.read_tier = kBlockCacheTier;
  }

  return ReadFilterBlock(table_, nullptr /* prefetch_buffer */, ro,
                         cache_filter_blocks(), get_context, lookup_context,
                         filter_block);
}

template <typename TBlocklike>
size_t FilterBlockReaderCommon<TBlocklike>::ApproximateFilterBlockMemoryUsage()
    const {
  assert(!filter_block_.GetOwnValue() || filter_block_.GetValue() != nullptr);
  return filter_block_.GetOwnValue()
             ? filter_block_.GetValue()->ApproximateMemoryUsage()
             : 0;
}

template <typename TBlocklike>
bool FilterBlockReaderCommon<TBlocklike>::RangeMayExist(
    const Slice* iterate_upper_bound, const Slice& user_key_without_ts,
    const SliceTransform* prefix_extractor, const Comparator* comparator,
    const Slice* const const_ikey_ptr, bool* filter_checked,
    bool need_upper_bound_check, bool no_io,
    BlockCacheLookupContext* lookup_context, const ReadOptions& read_options) {
  if (!prefix_extractor || !prefix_extractor->InDomain(user_key_without_ts)) {
    *filter_checked = false;
    return true;
  }
  Slice prefix = prefix_extractor->Transform(user_key_without_ts);
  if (need_upper_bound_check &&
      !IsFilterCompatible(iterate_upper_bound, prefix, comparator)) {
    *filter_checked = false;
    return true;
  } else {
    *filter_checked = true;
    return PrefixMayMatch(prefix, no_io, const_ikey_ptr,
                          /* get_context */ nullptr, lookup_context,
                          read_options);
  }
}

template <typename TBlocklike>
bool FilterBlockReaderCommon<TBlocklike>::IsFilterCompatible(
    const Slice* iterate_upper_bound, const Slice& prefix,
    const Comparator* comparator) const {
  // Try to reuse the bloom filter in the SST table if prefix_extractor in
  // mutable_cf_options has changed. If range [user_key, upper_bound) all
  // share the same prefix then we may still be able to use the bloom filter.
  const SliceTransform* const prefix_extractor = table_prefix_extractor();
  if (iterate_upper_bound != nullptr && prefix_extractor) {
    if (!prefix_extractor->InDomain(*iterate_upper_bound)) {
      return false;
    }
    Slice upper_bound_xform = prefix_extractor->Transform(*iterate_upper_bound);
    // first check if user_key and upper_bound all share the same prefix
    if (comparator->CompareWithoutTimestamp(prefix, false, upper_bound_xform,
                                            false) != 0) {
      // second check if user_key's prefix is the immediate predecessor of
      // upper_bound and have the same length. If so, we know for sure all
      // keys in the range [user_key, upper_bound) share the same prefix.
      // Also need to make sure upper_bound are full length to ensure
      // correctness
      if (!full_length_enabled_ ||
          iterate_upper_bound->size() != prefix_extractor_full_length_ ||
          !comparator->IsSameLengthImmediateSuccessor(prefix,
                                                      *iterate_upper_bound)) {
        return false;
      }
    }
    return true;
  } else {
    return false;
  }
}

// Explicitly instantiate templates for both "blocklike" types we use.
// This makes it possible to keep the template definitions in the .cc file.
template class FilterBlockReaderCommon<Block_kFilterPartitionIndex>;
template class FilterBlockReaderCommon<ParsedFullFilterBlock>;

}  // namespace ROCKSDB_NAMESPACE
