void TransactionLevelGCManager::DeleteFromIndexes(const std::shared_ptr<GarbageContext>& garbage_ctx) {

  GCSetType gc_set_type = garbage_ctx->gc_set_type_;
  
  if (gc_set_type == GC_SET_TYPE_COMMITTED) {
    // if the transaction is committed, 
    // then we need to remove tuples that are deleted by the transaction from indexes.
    for (auto entry : *(garbage_ctx->gc_set_.get())) {
      for (auto &element : entry.second) {
        if (element.second == RW_TYPE_DELETE || element.second == RW_TYPE_INS_DEL) {
          // only old versions are stored in the gc set.
          // so we can safely get indirection from the indirection array.
          auto tile_group = catalog::Manager::GetInstance().GetTileGroup(entry.first);
          if (tile_group != nullptr){
            auto tile_group_header = catalog::Manager::GetInstance()
                                         .GetTileGroup(entry.first)
                                         ->GetHeader();
            ItemPointer *indirection = tile_group_header->GetIndirection(element.first);

            DeleteTupleFromIndexes(indirection);
          }
        }
      }
    }

  } else {
    PL_ASSERT(gc_set_type == GC_SET_TYPE_ABORTED);

    for (auto entry : *(garbage_ctx->gc_set_.get())) {
      for (auto &element : entry.second) {
        if (element.second == RW_TYPE_INSERT || element.second == RW_TYPE_INS_DEL) {
          auto tile_group_header = catalog::Manager::GetInstance()
                                       .GetTileGroup(entry.first)
                                       ->GetHeader();
          ItemPointer *indirection = tile_group_header->GetIndirection(element.first);
          DeleteTupleFromIndexes(indirection);

        }
      }
    }
  }

}
void TimestampOrderingTransactionManager::PerformUpdate(
    TransactionContext *const current_txn, const ItemPointer &location,
    const ItemPointer &new_location) {
  PL_ASSERT(current_txn->GetIsolationLevel() != IsolationLevelType::READ_ONLY);

  ItemPointer old_location = location;

  LOG_TRACE("Performing Update old tuple %u %u", old_location.block,
            old_location.offset);
  LOG_TRACE("Performing Update new tuple %u %u", new_location.block,
            new_location.offset);

  auto &manager = catalog::Manager::GetInstance();

  auto tile_group_header =
      manager.GetTileGroup(old_location.block)->GetHeader();
  auto new_tile_group_header =
      manager.GetTileGroup(new_location.block)->GetHeader();

  auto transaction_id = current_txn->GetTransactionId();
  // if we can perform update, then we must have already locked the older
  // version.
  PL_ASSERT(tile_group_header->GetTransactionId(old_location.offset) ==
            transaction_id);
  PL_ASSERT(tile_group_header->GetPrevItemPointer(old_location.offset)
                .IsNull() == true);

  // check whether the new version is empty.
  PL_ASSERT(new_tile_group_header->GetTransactionId(new_location.offset) ==
            INVALID_TXN_ID);
  PL_ASSERT(new_tile_group_header->GetBeginCommitId(new_location.offset) ==
            MAX_CID);
  PL_ASSERT(new_tile_group_header->GetEndCommitId(new_location.offset) ==
            MAX_CID);

  // if the executor doesn't call PerformUpdate after AcquireOwnership,
  // no one will possibly release the write lock acquired by this txn.

  // Set double linked list
  tile_group_header->SetPrevItemPointer(old_location.offset, new_location);

  new_tile_group_header->SetNextItemPointer(new_location.offset, old_location);

  new_tile_group_header->SetTransactionId(new_location.offset, transaction_id);

  // we should guarantee that the newer version is all set before linking the
  // newer version to older version.
  COMPILER_MEMORY_FENCE;

  InitTupleReserved(new_tile_group_header, new_location.offset);

  // we must be updating the latest version.
  // Set the header information for the new version
  ItemPointer *index_entry_ptr =
      tile_group_header->GetIndirection(old_location.offset);

  // if there's no primary index on a table, then index_entry_ptr == nullptr.
  if (index_entry_ptr != nullptr) {
    new_tile_group_header->SetIndirection(new_location.offset, index_entry_ptr);

    // Set the index header in an atomic way.
    // We do it atomically because we don't want any one to see a half-done
    // pointer.
    // In case of contention, no one can update this pointer when we are
    // updating it
    // because we are holding the write lock. This update should success in
    // its first trial.
    UNUSED_ATTRIBUTE auto res =
        AtomicUpdateItemPointer(index_entry_ptr, new_location);
    PL_ASSERT(res == true);
  }

  // Add the old tuple into the update set
  current_txn->RecordUpdate(old_location);

  // Increment table update op stats
  if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode)) !=
      StatsType::INVALID) {
    stats::BackendStatsContext::GetInstance()->IncrementTableUpdates(
        new_location.block);
  }
}
bool TimestampOrderingTransactionManager::PerformRead(
    TransactionContext *const current_txn, const ItemPointer &read_location,
    bool acquire_ownership) {
  ItemPointer location = read_location;

  //////////////////////////////////////////////////////////
  //// handle READ_ONLY
  //////////////////////////////////////////////////////////
  if (current_txn->GetIsolationLevel() == IsolationLevelType::READ_ONLY) {
    // do not update read set for read-only transactions.
    return true;
  }  // end READ ONLY

  //////////////////////////////////////////////////////////
  //// handle SNAPSHOT
  //////////////////////////////////////////////////////////

  // TODO: what if we want to read a version that we write?
  else if (current_txn->GetIsolationLevel() == IsolationLevelType::SNAPSHOT) {
    oid_t tile_group_id = location.block;
    oid_t tuple_id = location.offset;

    LOG_TRACE("PerformRead (%u, %u)\n", location.block, location.offset);
    auto &manager = catalog::Manager::GetInstance();
    auto tile_group_header = manager.GetTileGroup(tile_group_id)->GetHeader();

    // Check if it's select for update before we check the ownership
    // and modify the last reader cid
    if (acquire_ownership == true) {
      // get the latest version of this tuple.
      location = *(tile_group_header->GetIndirection(location.offset));

      tile_group_id = location.block;
      tuple_id = location.offset;

      tile_group_header = manager.GetTileGroup(tile_group_id)->GetHeader();

      if (IsOwner(current_txn, tile_group_header, tuple_id) == false) {
        // Acquire ownership if we haven't
        if (IsOwnable(current_txn, tile_group_header, tuple_id) == false) {
          // Cannot own
          return false;
        }
        if (AcquireOwnership(current_txn, tile_group_header, tuple_id) ==
            false) {
          // Cannot acquire ownership
          return false;
        }

        // Record RWType::READ_OWN
        current_txn->RecordReadOwn(location);
      }

      // if we have already owned the version.
      PL_ASSERT(IsOwner(current_txn, tile_group_header, tuple_id) == true);

      // Increment table read op stats
      if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode)) !=
          StatsType::INVALID) {
        stats::BackendStatsContext::GetInstance()->IncrementTableReads(
            location.block);
      }

      return true;

    } else {
      // if it's not select for update, then update read set and return true.

      current_txn->RecordRead(location);

      // Increment table read op stats
      if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode)) !=
          StatsType::INVALID) {
        stats::BackendStatsContext::GetInstance()->IncrementTableReads(
            location.block);
      }
      return true;
    }

  }  // end SNAPSHOT

  //////////////////////////////////////////////////////////
  //// handle READ_COMMITTED
  //////////////////////////////////////////////////////////
  else if (current_txn->GetIsolationLevel() ==
           IsolationLevelType::READ_COMMITTED) {
    oid_t tile_group_id = location.block;
    oid_t tuple_id = location.offset;

    LOG_TRACE("PerformRead (%u, %u)\n", location.block, location.offset);
    auto &manager = catalog::Manager::GetInstance();
    auto tile_group_header = manager.GetTileGroup(tile_group_id)->GetHeader();

    // Check if it's select for update before we check the ownership.
    if (acquire_ownership == true) {
      // acquire ownership.
      if (IsOwner(current_txn, tile_group_header, tuple_id) == false) {
        // Acquire ownership if we haven't
        if (IsOwnable(current_txn, tile_group_header, tuple_id) == false) {
          // Cannot own
          return false;
        }
        if (AcquireOwnership(current_txn, tile_group_header, tuple_id) ==
            false) {
          // Cannot acquire ownership
          return false;
        }

        // Record RWType::READ_OWN
        current_txn->RecordReadOwn(location);
      }
      // if we have already owned the version.
      PL_ASSERT(IsOwner(current_txn, tile_group_header, tuple_id) == true);
      // Increment table read op stats
      if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode)) !=
          StatsType::INVALID) {
        stats::BackendStatsContext::GetInstance()->IncrementTableReads(
            location.block);
      }
      return true;

    } else {
      // a transaction can never read an uncommitted version.
      if (IsOwner(current_txn, tile_group_header, tuple_id) == false) {
        if (IsOwned(current_txn, tile_group_header, tuple_id) == false) {
          current_txn->RecordRead(location);

          // Increment table read op stats
          if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode))
              != StatsType::INVALID) {
            stats::BackendStatsContext::GetInstance()->IncrementTableReads(
                location.block);
          }
          return true;

        } else {
          // if the tuple has been owned by some concurrent transactions,
          // then read fails.
          LOG_TRACE("Transaction read failed");
          return false;
        }

      } else {
        // this version must already be in the read/write set.
        // so no need to update read set.
        // current_txn->RecordRead(location);

        // Increment table read op stats
        if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode))
            != StatsType::INVALID) {
          stats::BackendStatsContext::GetInstance()->IncrementTableReads(
              location.block);
        }
        return true;
      }
    }

  }  // end READ_COMMITTED

  //////////////////////////////////////////////////////////
  //// handle SERIALIZABLE and REPEATABLE_READS
  //////////////////////////////////////////////////////////
  else {
    PL_ASSERT(current_txn->GetIsolationLevel() ==
                  IsolationLevelType::SERIALIZABLE ||
              current_txn->GetIsolationLevel() ==
                  IsolationLevelType::REPEATABLE_READS);

    oid_t tile_group_id = location.block;
    oid_t tuple_id = location.offset;

    LOG_TRACE("PerformRead (%u, %u)\n", location.block, location.offset);
    auto &manager = catalog::Manager::GetInstance();
    auto tile_group_header = manager.GetTileGroup(tile_group_id)->GetHeader();

    // Check if it's select for update before we check the ownership
    // and modify the last reader cid.
    if (acquire_ownership == true) {
      // acquire ownership.
      if (IsOwner(current_txn, tile_group_header, tuple_id) == false) {
        // Acquire ownership if we haven't
        if (IsOwnable(current_txn, tile_group_header, tuple_id) == false) {
          // Cannot own
          return false;
        }
        if (AcquireOwnership(current_txn, tile_group_header, tuple_id) ==
            false) {
          // Cannot acquire ownership
          return false;
        }

        // Record RWType::READ_OWN
        current_txn->RecordReadOwn(location);

        // now we have already obtained the ownership.
        // then attempt to set last reader cid.
        UNUSED_ATTRIBUTE bool ret = SetLastReaderCommitId(
            tile_group_header, tuple_id, current_txn->GetCommitId(), true);

        PL_ASSERT(ret == true);
        // there's no need to maintain read set for timestamp ordering protocol.
        // T/O does not check the read set during commit phase.
      }

      // if we have already owned the version.
      PL_ASSERT(IsOwner(current_txn, tile_group_header, tuple_id) == true);
      PL_ASSERT(GetLastReaderCommitId(tile_group_header, tuple_id) ==
                    current_txn->GetCommitId() ||
                GetLastReaderCommitId(tile_group_header, tuple_id) == 0);
      // Increment table read op stats
      if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode)) !=
          StatsType::INVALID) {
        stats::BackendStatsContext::GetInstance()->IncrementTableReads(
            location.block);
      }
      return true;

    } else {
      if (IsOwner(current_txn, tile_group_header, tuple_id) == false) {
        // if the current transaction does not own this tuple,
        // then attempt to set last reader cid.
        if (SetLastReaderCommitId(tile_group_header, tuple_id,
                                  current_txn->GetCommitId(), false) == true) {
          // update read set.
          current_txn->RecordRead(location);

          // Increment table read op stats
          if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode))
              != StatsType::INVALID) {
            stats::BackendStatsContext::GetInstance()->IncrementTableReads(
                location.block);
          }
          return true;
        } else {
          // if the tuple has been owned by some concurrent transactions,
          // then read fails.
          LOG_TRACE("Transaction read failed");
          return false;
        }

      } else {
        // if the current transaction has already owned this tuple,
        // then perform read directly.
        PL_ASSERT(GetLastReaderCommitId(tile_group_header, tuple_id) ==
                      current_txn->GetCommitId() ||
                  GetLastReaderCommitId(tile_group_header, tuple_id) == 0);

        // this version must already be in the read/write set.
        // so no need to update read set.
        // current_txn->RecordRead(location);

        // Increment table read op stats
        if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode))
            != StatsType::INVALID) {
          stats::BackendStatsContext::GetInstance()->IncrementTableReads(
              location.block);
        }
        return true;
      }
    }

  }  // end SERIALIZABLE || REPEATABLE_READS
}