/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include "ob_sstable_estimator.h"
#include "storage/blocksstable/ob_micro_block_scanner.h"
#include "storage/ob_file_system_util.h"

namespace oceanbase {
using namespace common;
using namespace blocksstable;
namespace storage {
const int64_t MACRO_BLOCK_COUNT_THRESHOLD = 1024;

ObSSTableEstimateContext::ObSSTableEstimateContext() : sstable_(NULL), rowkeys_(NULL)
{}

ObSSTableEstimateContext::~ObSSTableEstimateContext()
{}

void ObSSTableEstimateContext::reset()
{
  sstable_ = NULL;
  rowkeys_ = NULL;
  macro_blocks_.reset();
  cache_context_.reset();
  multi_version_range_.reset();
}

bool ObSSTableEstimateContext::is_valid() const
{
  return NULL != sstable_ && NULL != rowkeys_ && cache_context_.is_valid();
}

ObStoreRowMultiGetEstimator::ObStoreRowMultiGetEstimator() : ObISSTableEstimator()
{}

ObStoreRowMultiGetEstimator::~ObStoreRowMultiGetEstimator()
{}

int ObStoreRowMultiGetEstimator::set_context(ObSSTableEstimateContext& context)
{
  int ret = OB_SUCCESS;
  if (OB_FAIL(ObISSTableEstimator::set_context(context))) {
    STORAGE_LOG(WARN, "failed to set context", K(ret));
  } else if (OB_FAIL(context_.sstable_->find_macros(*context_.rowkeys_, context_.macro_blocks_))) {
    STORAGE_LOG(WARN, "failed to find macro blocks", K(ret));
  }
  return ret;
}

int ObStoreRowMultiGetEstimator::estimate_row_count(ObPartitionEst& part_est)
{
  int ret = OB_SUCCESS;
  int tmp_ret = OB_SUCCESS;
  bool is_contain = true;
  ObRowValueHandle handle;
  ObStorageFileHandle file_handle;
  ObStorageFile* file = nullptr;
  if (OB_FAIL(file_handle.assign(context_.sstable_->get_storage_file_handle()))) {
    STORAGE_LOG(WARN, "fail to get file handle", K(ret), K(context_.sstable_->get_storage_file_handle()));
  } else if (OB_ISNULL(file = file_handle.get_storage_file())) {
    ret = OB_ERR_UNEXPECTED;
    STORAGE_LOG(WARN, "fail to get pg file", K(ret), K(file_handle));
  } else {
    for (int64_t i = 0; OB_SUCC(ret) && i < context_.rowkeys_->count(); ++i) {
      const ObStoreRowkey& rowkey = context_.rowkeys_->at(i).get_store_rowkey();
      const ObMacroBlockCtx& block_ctx = context_.macro_blocks_.at(i);
      const MacroBlockId& block_id = block_ctx.get_macro_block_id();
      if (block_id.is_valid()) {
        if (OB_SUCCESS != (tmp_ret = context_.cache_context_.bf_cache_->may_contain(
                               context_.sstable_->get_table_id(), block_id, file->get_file_id(), rowkey, is_contain))) {
          if (OB_ENTRY_NOT_EXIST != tmp_ret) {
            STORAGE_LOG(WARN, "failed to check may contain", K(tmp_ret), K_(context), K(block_ctx), K(rowkey));
          }
        }
        if (is_contain || (OB_SUCCESS != ret)) {
          part_est.logical_row_count_++;
        }
      }
    }
  }
  return ret;
}

int ObSSTableScanEstimator::alloc_estimator(const ObSSTable& sstable)
{
  int ret = OB_SUCCESS;
  void* buf = NULL;
  if (sstable.is_multi_version_minor_sstable()) {
    if (OB_ISNULL(buf = allocator_.alloc(sizeof(ObMultiVersionSingleScanEstimator)))) {
      ret = OB_ALLOCATE_MEMORY_FAILED;
    } else {
      estimator_ = new (buf) ObMultiVersionSingleScanEstimator(allocator_);
    }
  } else {
    if (OB_ISNULL(buf = allocator_.alloc(sizeof(ObStoreRowSingleScanEstimator)))) {
      ret = OB_ALLOCATE_MEMORY_FAILED;
      STORAGE_LOG(WARN, "failed to alloc ObStoreRowSingleScanEstimator", K(ret));
    } else {
      estimator_ = new (buf) ObStoreRowSingleScanEstimator();
    }
  }
  return ret;
}

ObStoreRowSingleScanEstimator::ObStoreRowSingleScanEstimator()
    : ObISSTableEstimator(), is_empty_scan_(false), is_first_scan_(true)
{}

ObStoreRowSingleScanEstimator::~ObStoreRowSingleScanEstimator()
{}

int ObStoreRowSingleScanEstimator::check_bf(ObMacroBlockCtx& macro_block_ctx)
{
  int ret = OB_SUCCESS;
  int tmp_ret = OB_SUCCESS;
  // check bloom filter to identify if the scan is empty
  const MacroBlockId& macro_block_id = macro_block_ctx.get_macro_block_id();
  ObStoreRowkey rowkey;
  ObStorageFileHandle file_handle;
  ObStorageFile* file = nullptr;
  if (OB_FAIL(file_handle.assign(context_.sstable_->get_storage_file_handle()))) {
    STORAGE_LOG(WARN, "fail to get file handle", K(ret), K(context_.sstable_->get_storage_file_handle()));
  } else if (OB_ISNULL(file = file_handle.get_storage_file())) {
    ret = OB_ERR_UNEXPECTED;
    STORAGE_LOG(WARN, "fail to get pg file", K(ret), K(file_handle));
  } else if (OB_FAIL(get_common_rowkey(context_.range_->get_range(), rowkey))) {
    STORAGE_LOG(WARN, "failed to get common rowkey", K(ret), K(context_.range_));
  } else if (rowkey.get_obj_cnt() > 0) {
    // allow check contain fail, should not overwrite ret
    bool is_contain = false;
    if (OB_SUCCESS !=
        (tmp_ret = context_.cache_context_.bf_cache_->may_contain(
             context_.sstable_->get_table_id(), macro_block_id, file->get_file_id(), rowkey, is_contain))) {
      if (OB_ENTRY_NOT_EXIST != tmp_ret) {
        STORAGE_LOG(WARN, "failed to check may contain", K(tmp_ret), K_(context), K(macro_block_id), K(rowkey));
      }
    } else if (!is_contain) {
      is_empty_scan_ = true;
    }
  }

  return ret;
}

void ObStoreRowSingleScanEstimator::reset()
{
  ObISSTableEstimator::reset();
  is_empty_scan_ = false;
  is_first_scan_ = true;
}

int ObStoreRowSingleScanEstimator::estimate_row_count(ObPartitionEst& part_est)
{
  int ret = OB_SUCCESS;
  int64_t total_macro_block_count = 0;
  ObMacroBlockIterator macro_iter;

  if (!context_.range_->get_range().is_valid()) {
  } else if (context_.range_->get_range().is_whole_range()) {
    part_est.logical_row_count_ = context_.sstable_->get_meta().row_count_;
    part_est.physical_row_count_ = part_est.logical_row_count_;
  } else if (OB_FAIL(macro_iter.open(*context_.sstable_, *context_.range_))) {
    STORAGE_LOG(WARN, "fail to open macro iter,", K(ret));
  } else if (OB_FAIL(macro_iter.get_macro_block_count(total_macro_block_count))) {
    STORAGE_LOG(WARN, "fail to get macro block count,", K(ret));
  } else if (0 == total_macro_block_count) {
  } else if (total_macro_block_count > MACRO_BLOCK_COUNT_THRESHOLD) {
    // there are too many block, do estimate in fast way
    part_est.logical_row_count_ = (double)total_macro_block_count / context_.sstable_->get_meta().macro_block_count_ *
                                  context_.sstable_->get_meta().row_count_;
    part_est.physical_row_count_ = part_est.logical_row_count_;
  } else {
    // do estimate by macro block scan
    ObMacroBlockCtx macro_block_ctx;
    int64_t idx = 0;
    while (OB_SUCC(ret)) {
      bool is_start_block = 0 == idx;
      bool is_last_block = total_macro_block_count - 1 == idx;
      if (OB_FAIL(macro_iter.get_next_macro_block(macro_block_ctx))) {
        if (OB_ITER_END != ret) {
          STORAGE_LOG(WARN, "fail to get next macro block, ", K(ret));
        } else {
          ret = OB_SUCCESS;
          break;
        }
      } else {
        if (1 == total_macro_block_count && OB_FAIL(check_bf(macro_block_ctx))) {
          STORAGE_LOG(WARN, "failed to open single scan estimator", K(ret));
        } else if (is_empty_scan_) {
        } else if (OB_FAIL(estimate_macro_row_count(macro_block_ctx, is_start_block, is_last_block, part_est))) {
          STORAGE_LOG(WARN,
              "cannot estimate cost of macro block.",
              K(ret),
              K(macro_block_ctx),
              K(idx),
              K(total_macro_block_count));
        }
      }
      idx++;
    }
    part_est.physical_row_count_ = part_est.logical_row_count_;
  }
  return ret;
}

int ObStoreRowSingleScanEstimator::estimate_macro_row_count(const ObMacroBlockCtx& macro_block_ctx,
    const bool is_start_block, const bool is_last_block, ObPartitionEst& part_est)
{
  int ret = OB_SUCCESS;

  if (!macro_block_ctx.is_valid()) {
    ret = OB_INVALID_ARGUMENT;
    STORAGE_LOG(WARN, "invalid args", K(ret), K(macro_block_ctx));
  } else {
    ObFullMacroBlockMeta meta;
    ObArray<ObMicroBlockInfo> micro_infos;

    if (OB_FAIL(context_.sstable_->get_meta(macro_block_ctx.get_macro_block_id(), meta))) {
      STORAGE_LOG(WARN, "Fail to get macro meta, ", K(ret), K(macro_block_ctx));
    } else if (!meta.is_valid()) {
      ret = OB_ERR_UNEXPECTED;
      STORAGE_LOG(WARN, "The macro block meta is NULL, ", K(ret), K(macro_block_ctx));
    } else if ((!is_start_block) && (!is_last_block)) {
      // in the middle of scan ranges;
      part_est.logical_row_count_ += meta.meta_->row_count_;
    } else if (OB_FAIL(context_.cache_context_.block_index_cache_->get_micro_infos(context_.sstable_->get_table_id(),
                   macro_block_ctx,
                   context_.range_->get_range(),
                   is_start_block,
                   is_last_block,
                   micro_infos))) {
      if (OB_BEYOND_THE_RANGE == ret) {
        ret = OB_SUCCESS;
      } else {
        STORAGE_LOG(WARN, "failed to get micro infos", K(ret), K_(context), K(macro_block_ctx));
      }
    } else {
      // FIXME:
      // 1. io_micro_block_ costs need multiply block cache hit percent.
      // 2. row costs are not precise, need check in micro block.
      int64_t average_row_count = 0;
      int64_t average_row_size = 0;
      int64_t micro_block_count = micro_infos.count();
      if (0 != meta.meta_->micro_block_count_) {
        average_row_count = meta.meta_->row_count_ / meta.meta_->micro_block_count_;
      }
      if (0 != meta.meta_->row_count_) {
        average_row_size =
            (meta.meta_->micro_block_index_offset_ - meta.meta_->micro_block_data_offset_) / meta.meta_->row_count_;
      }
      if (OB_SUCCESS == ret && is_start_block && micro_block_count > 0) {
        if (OB_FAIL(estimate_border_cost(micro_infos,
                macro_block_ctx,
                true /*check_start*/,
                average_row_count,
                average_row_size,
                micro_block_count,
                part_est))) {
          STORAGE_LOG(WARN, "failed to estimate border cost", K(ret));
        }
      }

      if (OB_SUCCESS == ret && is_last_block && micro_block_count > 0) {
        if (OB_FAIL(estimate_border_cost(micro_infos,
                macro_block_ctx,
                false /*check_start*/,
                average_row_count,
                average_row_size,
                micro_block_count,
                part_est))) {
          STORAGE_LOG(WARN, "failed to estimate border cost", K(ret));
        }
      }

      if (OB_SUCCESS == ret && micro_block_count > 0) {
        int64_t other_row_count = average_row_count * micro_block_count;
        part_est.logical_row_count_ += other_row_count;
      }
    }
  }
  return ret;
}

int ObStoreRowSingleScanEstimator::estimate_border_cost(
    const common::ObIArray<blocksstable::ObMicroBlockInfo>& micro_infos, const ObMacroBlockCtx& macro_block_ctx,
    const bool check_start, const int64_t average_row_count, const int64_t average_row_size, int64_t& micro_block_count,
    ObPartitionEst& part_est)
{
  int ret = OB_SUCCESS;
  int64_t result_row_count = 0;
  int64_t first = check_start ? 0 : micro_infos.count() - 1;

  if (first < 0) {
    ret = OB_ERR_SYS;
    STORAGE_LOG(WARN, "invalid first", K(ret), K(first), K(micro_infos.count()), K(check_start));
  } else if (!macro_block_ctx.is_valid() || average_row_count < 0 || average_row_size < 0) {
    ret = OB_INVALID_ARGUMENT;
    STORAGE_LOG(WARN, "invalid args", K(ret), K(macro_block_ctx), K(average_row_count), K(average_row_size));
  } else if (micro_block_count > 0) {
    if (OB_FAIL(estimate_border_count_(
            micro_infos.at(first), macro_block_ctx, average_row_size, result_row_count, micro_block_count, part_est))) {
      STORAGE_LOG(WARN, "fail to estimate_border_block_row_count, ", K(ret));
    } else if (micro_block_count > 0 && 0 == result_row_count) {
      int64_t second = check_start ? 1 : micro_infos.count() - 2;
      if (second < 0) {
        ret = OB_ERR_SYS;
        STORAGE_LOG(WARN, "invalid second , ", K(ret), K(second), K(check_start), K(micro_infos.count()));
      } else if (OB_FAIL(estimate_border_count_(micro_infos.at(second),
                     macro_block_ctx,
                     average_row_size,
                     result_row_count,
                     micro_block_count,
                     part_est))) {
        STORAGE_LOG(WARN, "fail to estimate_border_block_row_count, ", K(ret));
      }
    }
  }
  return ret;
}

int ObStoreRowSingleScanEstimator::estimate_border_count_(const ObMicroBlockInfo& micro_info,
    const blocksstable::ObMacroBlockCtx& macro_block_ctx, const int64_t average_row_size, int64_t& result_row_count,
    int64_t& micro_block_count, ObPartitionEst& part_est)
{
  int ret = OB_SUCCESS;
  int64_t logical_row_count = 0;

  if (!micro_info.is_valid() || !macro_block_ctx.is_valid() || average_row_size < 0) {
    ret = OB_INVALID_ARGUMENT;
    STORAGE_LOG(WARN, "invalid args, ", K(ret), K(micro_info), K(macro_block_ctx), K(average_row_size));
  } else if (OB_FAIL(
                 estimate_border_row_count(micro_info, macro_block_ctx, false, logical_row_count, result_row_count))) {
    STORAGE_LOG(WARN, "failed to estimate_border_row_count, ", K(ret));
  } else if (result_row_count < 0) {
    ret = OB_ERR_SYS;
    STORAGE_LOG(ERROR, "invalid result_row_count, ", K(ret), K(result_row_count));
  } else {
    --micro_block_count;
    part_est.logical_row_count_ += logical_row_count;
  }

  return ret;
}

int ObISSTableEstimator::estimate_border_row_count(const ObMicroBlockInfo& micro_info,
    const blocksstable::ObMacroBlockCtx& macro_block_ctx, bool consider_multi_version, int64_t& logical_row_count,
    int64_t& physical_row_count)
{
  int ret = OB_SUCCESS;
  ObFullMacroBlockMeta meta;
  ObBlockCacheWorkingSet* block_cache_ws = context_.cache_context_.block_cache_ws_;
  ObMicroBlockBufferHandle micro_handle;
  ObMicroBlockData block_data;
  ObMacroBlockReader reader;
  ObMacroBlockHandle macro_handle;
  ObArenaAllocator allocator;
  ObStorageFileHandle file_handle;
  ObStorageFile* file = nullptr;
  logical_row_count = 0;
  physical_row_count = 0;

  if (!micro_info.is_valid() || !macro_block_ctx.is_valid()) {
    ret = OB_INVALID_ARGUMENT;
    STORAGE_LOG(WARN, "invalid args", K(ret), K(micro_info), K(macro_block_ctx), K(physical_row_count));
  } else if (NULL == block_cache_ws) {
    ret = OB_ERR_SYS;
    STORAGE_LOG(ERROR, "block cache ws must not null", K(ret));
  } else if (OB_FAIL(file_handle.assign(context_.sstable_->get_storage_file_handle()))) {
    STORAGE_LOG(WARN, "fail to get file handle", K(ret), K(context_.sstable_->get_storage_file_handle()));
  } else if (OB_ISNULL(file = file_handle.get_storage_file())) {
    ret = OB_ERR_UNEXPECTED;
    STORAGE_LOG(WARN, "fail to get pg file", K(ret), K(file_handle));
  } else if (OB_FAIL(context_.sstable_->get_meta(macro_block_ctx.get_macro_block_id(), meta))) {
    STORAGE_LOG(WARN, "Fail to get macro meta, ", K(ret));
  } else if (!meta.is_valid()) {
    ret = OB_ERR_UNEXPECTED;
    STORAGE_LOG(WARN, "unexpect error, meta is NULL.", K(ret), K(meta), KP(block_cache_ws), K(macro_block_ctx));
  } else if (OB_FAIL(block_cache_ws->get_cache_block(context_.sstable_->get_table_id(),
                 macro_block_ctx.get_macro_block_id(),
                 file->get_file_id(),
                 micro_info.offset_,
                 micro_info.size_,
                 micro_handle))) {
    if (OB_ENTRY_NOT_EXIST != ret) {
      STORAGE_LOG(WARN, "failed to get cache block", K(ret), K_(context), K(macro_block_ctx));
    } else {
      // we cannot always get micro block from block cache
      // now we still need to load data from disk and overwrite the return code
      bool is_compressed = false;
      ObMacroBlockReadInfo read_info;
      read_info.macro_block_ctx_ = &macro_block_ctx;
      read_info.offset_ = micro_info.offset_;
      read_info.size_ = micro_info.size_;
      read_info.io_desc_.category_ = USER_IO;
      read_info.io_desc_.wait_event_no_ = ObWaitEventIds::DB_FILE_DATA_READ;
      macro_handle.set_file(file);
      if (OB_FAIL(file->read_block(read_info, macro_handle))) {
        STORAGE_LOG(WARN, "Fail to read micro block from io, ", K(ret));
      } else if (OB_FAIL(ObRecordHeaderV3::deserialize_and_check_record(
                     macro_handle.get_buffer(), micro_info.size_, MICRO_BLOCK_HEADER_MAGIC))) {
        STORAGE_LOG(WARN, "micro block data is corrupted.", K(ret));
      } else if (OB_FAIL(reader.decompress_data(meta,
                     macro_handle.get_buffer(),
                     micro_info.size_,
                     block_data.get_buf(),
                     block_data.get_buf_size(),
                     is_compressed))) {
        STORAGE_LOG(WARN, "Fail to decompress data, ", K(ret));
      } else {
        int tmp_ret = OB_SUCCESS;
        ObMicroBlockCacheKey tmp_key(context_.sstable_->get_table_id(),
            macro_block_ctx.get_macro_block_id(),
            file->get_file_id(),
            micro_info.offset_,
            micro_info.size_);
        ObMicroBlockCacheValue tmp_value(block_data.get_buf(), block_data.get_buf_size());
        if (OB_SUCCESS != (tmp_ret = block_cache_ws->put(tmp_key, tmp_value))) {
          STORAGE_LOG(WARN, "Fail to put micro block to block cache, ", K(tmp_ret));
        }
        STORAGE_LOG(DEBUG, "Success to decompress micro block, ", K(macro_block_ctx));
      }
    }
  } else if (!micro_handle.is_valid()) {
    ret = OB_ERR_SYS;
    STORAGE_LOG(ERROR, "invalid micro_handle", K(ret), K(micro_handle));
  } else {
    block_data = *micro_handle.get_block_data();
  }

  if (OB_SUCC(ret)) {
    ObMicroBlockScanner* block_scanner = NULL;
    ObColumnMap* column_map = NULL;
    common::ObArray<share::schema::ObColDesc> columns;

    if (NULL == (column_map = OB_NEW(ObColumnMap, ObModIds::OB_CS_SSTABLE_READER))) {
      ret = OB_ALLOCATE_MEMORY_FAILED;
      STORAGE_LOG(ERROR, "allocate memory for column map failed.", K(ret));
    } else if (NULL == (block_scanner = OB_NEW(ObMicroBlockScanner, ObModIds::OB_CS_SSTABLE_READER))) {
      ret = OB_ALLOCATE_MEMORY_FAILED;
      STORAGE_LOG(ERROR, "allocate memory for macro block scanner failed.", K(ret));
    } else if (OB_FAIL(columns.reserve(meta.meta_->rowkey_column_number_))) {
      STORAGE_LOG(WARN, "reserve memory for rowkey columns desc failed.", K(ret));
    } else if (OB_FAIL(get_rowkey_column_desc(meta, columns))) {
      STORAGE_LOG(WARN, "Fail to get_rowkey_column_desc ", K(ret));
    } else if (OB_FAIL(column_map->init(allocator,
                   meta.meta_->schema_version_,
                   meta.meta_->rowkey_column_number_,
                   meta.meta_->column_number_,
                   columns))) {
      STORAGE_LOG(WARN, "Fail to init column map, ", K(ret));
    } else if (OB_FAIL(block_scanner->estimate_row_count(allocator,
                   context_.range_->get_range(),
                   *column_map,
                   block_data,
                   static_cast<ObRowStoreType>(meta.meta_->row_store_type_),
                   consider_multi_version,
                   logical_row_count,
                   physical_row_count))) {
      STORAGE_LOG(WARN, "Fail to set scan param for micro block scanner, ", K(ret));
    }
    if (NULL != column_map) {
      OB_DELETE(ObColumnMap, ObModIds::OB_CS_SSTABLE_READER, column_map);
      column_map = NULL;
    }
    if (NULL != block_scanner) {
      OB_DELETE(ObMicroBlockScanner, ObModIds::OB_CS_SSTABLE_READER, block_scanner);
      block_scanner = NULL;
    }
  }

  return ret;
}

int ObISSTableEstimator::get_rowkey_column_desc(
    const blocksstable::ObFullMacroBlockMeta& meta, common::ObIArray<share::schema::ObColDesc>& columns)
{
  // static func, no need to check inner stat
  int ret = OB_SUCCESS;
  share::schema::ObColDesc desc;

  if (!meta.is_valid() || columns.count() != 0) {
    ret = OB_INVALID_ARGUMENT;
    STORAGE_LOG(WARN, "invalid args", K(ret), K(meta), K(columns));
  }
  for (int64_t i = 0; OB_SUCC(ret) && i < meta.meta_->rowkey_column_number_; ++i) {
    desc.col_id_ = meta.schema_->column_id_array_[i];
    desc.col_type_ = meta.schema_->column_type_array_[i];
    desc.col_order_ = meta.schema_->column_order_array_[i];
    if (OB_FAIL(columns.push_back(desc))) {
      STORAGE_LOG(WARN, "push column desc failed.", K(ret), K(desc));
    }
  }
  return ret;
}

int ObStoreRowSingleScanEstimator::get_common_rowkey(const ObStoreRange& range, ObStoreRowkey& rowkey)
{
  // static func, no need to check inner stat
  int ret = OB_SUCCESS;
  int64_t prefix_len = 0;

  if (!range.is_valid()) {
    ret = OB_INVALID_ARGUMENT;
    STORAGE_LOG(WARN, "invalid args", K(ret), K(range));
  } else if (OB_FAIL(ObStoreRowkey::get_common_prefix_length(range.get_start_key(), range.get_end_key(), prefix_len))) {
    STORAGE_LOG(WARN, "failed to get common prefix length", K(ret), K(range));
  } else {
    rowkey.reset();
    rowkey.assign(const_cast<ObObj*>(range.get_start_key().get_obj_ptr()), prefix_len);
  }

  return ret;
}

int ObMultiVersionSingleScanEstimator::set_context(ObSSTableEstimateContext& context)
{
  int ret = OB_SUCCESS;
  ObVersionRange version_range;
  version_range.snapshot_version_ = ObVersionRange::MAX_VERSION;
  version_range.base_version_ = ObVersionRange::MAX_VERSION;
  version_range.multi_version_start_ = ObVersionRange::MAX_VERSION;
  if (OB_FAIL(ObISSTableEstimator::set_context(context))) {
    STORAGE_LOG(WARN, "failed to set context", K(ret));
  } else if (OB_FAIL(ObVersionStoreRangeConversionHelper::range_to_multi_version_range(
                 *context.range_, version_range, allocator_, context_.multi_version_range_))) {
    STORAGE_LOG(WARN, "failed to convert to multi version range", K(ret));
  } else if (OB_FAIL(context_.sstable_->find_macros(context_.multi_version_range_, context_.macro_blocks_))) {
    STORAGE_LOG(WARN, "failed to find macros", K(ret), K_(context_.multi_version_range));
  }
  return ret;
}

int ObMultiVersionSingleScanEstimator::estimate_row_count(ObPartitionEst& part_est)
{
  int ret = OB_SUCCESS;
  int64_t total_macro_block_count = context_.macro_blocks_.count();

  MacroBlockId macro_block_id;
  bool is_left_border = false;
  bool is_right_border = false;
  int64_t gap_size = 0;
  int64_t purged_phy_row_count = 0;
  const bool is_whole_range = context_.range_->get_range().is_whole_range();

  for (int64_t i = 0; OB_SUCC(ret) && i < total_macro_block_count; ++i) {
    const ObMacroBlockCtx& macro_block_ctx = context_.macro_blocks_.at(i);
    is_left_border = is_whole_range ? false : 0 == i;
    is_right_border = is_whole_range ? false : total_macro_block_count - 1 == i;
    if (OB_FAIL(estimate_macro_row_count(
            macro_block_ctx, is_left_border, is_right_border, part_est, gap_size, purged_phy_row_count))) {
      STORAGE_LOG(WARN,
          "cannot estimate cost of macro block.",
          K(ret),
          K(macro_block_id),
          K(is_left_border),
          K(is_right_border),
          K(i),
          K(total_macro_block_count));
    }
  }
  return ret;
}

int ObMultiVersionSingleScanEstimator::estimate_macro_row_count(const blocksstable::ObMacroBlockCtx& macro_block_ctx,
    const bool is_left_border, const bool is_right_border, ObPartitionEst& part_est, int64_t& gap_size,
    int64_t& purged_phy_row_count)
{
  int ret = OB_SUCCESS;
  ObFullMacroBlockMeta meta;

  if (!macro_block_ctx.is_valid()) {
    ret = OB_INVALID_ARGUMENT;
    STORAGE_LOG(WARN, "macro block id is invalid", K(ret));
  } else if (OB_FAIL(context_.sstable_->get_meta(macro_block_ctx.get_macro_block_id(), meta))) {
    STORAGE_LOG(WARN, "failed to get macro block meta", K(ret), K(macro_block_ctx));
  } else if (!meta.is_valid()) {
    ret = OB_ERR_UNEXPECTED;
    STORAGE_LOG(WARN, "macro block meta is null", K(ret), K(macro_block_ctx));
  } else {
    const int64_t delta = meta.meta_->row_count_delta_;
    // if delta < 0, it means this macro block really delete rows already exist in older table
    // so if this delta is believable, we should minus gap_size+delta from physical row count
    // since rows from older table can be skipped
    int64_t macro_purged_row_count = 0;
    bool need_reset_gap = false;
    if (!(is_left_border || is_right_border)) {
      part_est.logical_row_count_ += meta.meta_->row_count_delta_;
      part_est.physical_row_count_ += meta.meta_->row_count_;
      if (meta.meta_->macro_block_deletion_flag_) {
        gap_size += meta.meta_->row_count_;
        purged_phy_row_count += meta.meta_->row_count_ - (delta < 0 ? delta : 0);
      } else {
        need_reset_gap = true;
        if (delta <= 0) {
          if (OB_FAIL(context_.cache_context_.block_index_cache_->cal_macro_purged_row_count(
                  context_.sstable_->get_table_id(), macro_block_ctx, macro_purged_row_count))) {
            STORAGE_LOG(WARN, "failed to cal_macro_phy_row_count", K(ret), K(macro_block_ctx));
          } else {
            part_est.physical_row_count_ -= macro_purged_row_count;
          }
        }
      }
    } else {
      need_reset_gap = true;
      bool need_check_micro_block = false;
      ObSEArray<ObMicroBlockInfo, 1> micro_infos;
      if (OB_FAIL(context_.cache_context_.block_index_cache_->cal_border_row_count(context_.sstable_->get_table_id(),
              macro_block_ctx,
              context_.multi_version_range_.get_range(),
              is_left_border,
              is_right_border,
              part_est.logical_row_count_,
              part_est.physical_row_count_,
              need_check_micro_block))) {
        STORAGE_LOG(WARN, "failed to get row count delta", K_(context), K(macro_block_ctx), K(ret));
      } else if (need_check_micro_block) {
        // Fix problem in issue #32813975 (the estimation is imprecise after minor freeze)
        // open the specific micro block to get the row count and take multiple versions of row keys
        // into consideration.
        int64_t logical_row_count = 0, physical_row_count = 0;
        if (OB_FAIL(context_.cache_context_.block_index_cache_->get_micro_infos(context_.sstable_->get_table_id(),
                macro_block_ctx,
                context_.multi_version_range_.get_range(),
                is_left_border,
                is_right_border,
                micro_infos))) {
          if (OB_BEYOND_THE_RANGE == ret) {
            ret = OB_SUCCESS;
          } else {
            STORAGE_LOG(WARN, "failed to get micro infos", K(ret), K_(context), K(macro_block_ctx));
          }
        } else if (1 != micro_infos.count()) {
          ret = OB_ERR_UNEXPECTED;
          STORAGE_LOG(WARN, "unexpected error, should only 1 micro block, ", K(ret), K(micro_infos.count()));
        } else if (OB_FAIL(estimate_border_row_count(
                       micro_infos.at(0), macro_block_ctx, true, logical_row_count, physical_row_count))) {
          STORAGE_LOG(WARN, "failed to estimate_border_row_count for multi version, ", K(ret));
        } else {
          part_est.logical_row_count_ = logical_row_count;
          part_est.physical_row_count_ = physical_row_count;
        }
      }
    }
    if (OB_SUCC(ret) && need_reset_gap) {
      if (gap_size >= OB_SKIP_RANGE_LIMIT) {
        part_est.physical_row_count_ -= purged_phy_row_count;
      }
      gap_size = 0;
      purged_phy_row_count = 0;
    }
    STORAGE_LOG(DEBUG,
        "estimate macro row count",
        K(part_est),
        K(delta),
        K(macro_block_ctx),
        K(gap_size),
        K(purged_phy_row_count));
  }
  return ret;
}

ObStoreRowMultiScanEstimator::ObStoreRowMultiScanEstimator() : ObISSTableEstimator()
{}

ObStoreRowMultiScanEstimator::~ObStoreRowMultiScanEstimator()
{}

int ObStoreRowMultiScanEstimator::estimate_row_count(ObPartitionEst& part_est)
{
  int ret = OB_SUCCESS;
  ObQueryFlag query_flag;
  ObArray<ObExtStoreRowkey> rowkeys;
  ObPartitionEst tmp_cost;
  for (int64_t i = 0; OB_SUCC(ret) && i < context_.ranges_->count(); ++i) {
    const ObExtStoreRange& range = context_.ranges_->at(i);
    if (OB_FAIL(context_.sstable_->estimate_scan_row_count(
            query_flag, context_.sstable_->get_table_id(), range, tmp_cost))) {
      STORAGE_LOG(WARN, "fail to estimate scan cost of sstable", K(ret));
    } else if (OB_FAIL(part_est.add(tmp_cost))) {
      STORAGE_LOG(WARN, "failed to add cost metrics", K(ret));
    }
  }

  return ret;
}

}  // namespace storage
}  // namespace oceanbase
