// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef SQL_RECOVER_MODULE_BTREE_H_
#define SQL_RECOVER_MODULE_BTREE_H_

#include <cstdint>
#include <ostream>

#include "base/check.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/sequence_checker.h"

namespace sql {
namespace recover {

class DatabasePageReader;

// Streaming decoder for inner pages in SQLite table B-trees.
//
// The decoder outputs the page IDs of the inner page's children pages.
//
// An instance can only be used to decode a single page. Instances are not
// thread-safe.
class InnerPageDecoder {
 public:
  // Creates a decoder for a DatabasePageReader's last read page.
  //
  // |db_reader| must have been used to read an inner page of a table B-tree.
  // |db_reader| must outlive this instance.
  explicit InnerPageDecoder(DatabasePageReader* db_reader) noexcept;
  ~InnerPageDecoder() noexcept = default;

  InnerPageDecoder(const InnerPageDecoder&) = delete;
  InnerPageDecoder& operator=(const InnerPageDecoder&) = delete;

  // The ID of the database page decoded by this instance.
  int page_id() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return page_id_;
  }

  // Returns true iff TryAdvance() may be called.
  bool CanAdvance() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    // The <= below is not a typo. Inner nodes store the right-most child
    // pointer in their headers, so their child count is (cell_count + 1).
    return next_read_index_ <= cell_count_;
  }

  // Advances the reader and returns the last read value.
  //
  // May return an invalid page ID if database was corrupted or the read failed.
  // The caller must use DatabasePageReader::IsValidPageId() to verify the
  // returned page ID. The caller should continue attempting to read as long as
  // CanAdvance() returns true.
  int TryAdvance();

  // True if the given reader may point to an inner page in a table B-tree.
  //
  // The last ReadPage() call on |db_reader| must have succeeded.
  static bool IsOnValidPage(DatabasePageReader* db_reader);

 private:
  // Returns the number of cells in the B-tree page.
  //
  // Checks for database corruption. The caller can assume that the cell pointer
  // array with the returned size will not extend past the page buffer.
  static int ComputeCellCount(DatabasePageReader* db_reader);

  // The number of the B-tree page this reader is reading.
  const int page_id_;
  // Used to read the tree page.
  //
  // Raw pointer usage is acceptable because this instance's owner is expected
  // to ensure that the DatabasePageReader outlives this.
  // This field is not a raw_ptr<> because it caused a
  // std::is_trivially_destructible static_assert failure.
  RAW_PTR_EXCLUSION DatabasePageReader* const db_reader_;
  // Caches the ComputeCellCount() value for this reader's page.
  const int cell_count_ = ComputeCellCount(db_reader_);

  // The reader's cursor state.
  //
  // Each B-tree page has a header and many cells. In an inner B-tree page, each
  // cell points to a child page, and the header points to the last child page.
  // So, an inner page with N cells has N+1 children, and |next_read_index_|
  // takes values between 0 and |cell_count_| + 1.
  int next_read_index_ = 0;

  SEQUENCE_CHECKER(sequence_checker_);
};

// Streaming decoder for leaf pages in SQLite table B-trees.
//
// Conceptually, the decoder outputs (rowid, record size, record offset) tuples
// for all the values stored in the leaf page. The tuple members can be accessed
// via last_record_{rowid, size, offset}() methods.
//
// An instance can only be used to decode a single page. Instances are not
// thread-safe.
class LeafPageDecoder {
 public:
  // Creates a decoder for a DatabasePageReader's last read page.
  //
  // |db_reader| must have been used to read an inner page of a table B-tree.
  // |db_reader| must outlive this instance.
  explicit LeafPageDecoder(DatabasePageReader* db_reader) noexcept;
  ~LeafPageDecoder() noexcept = default;

  LeafPageDecoder(const LeafPageDecoder&) = delete;
  LeafPageDecoder& operator=(const LeafPageDecoder&) = delete;

  // The rowid of the most recent record read by TryAdvance().
  //
  // Must only be called after a successful call to TryAdvance().
  int64_t last_record_rowid() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(last_record_size_ != 0)
        << "TryAdvance() not called / did not succeed";
    return last_record_rowid_;
  }

  // The size of the most recent record read by TryAdvance().
  //
  // Must only be called after a successful call to TryAdvance().
  int64_t last_record_size() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(last_record_size_ != 0)
        << "TryAdvance() not called / did not succeed";
    return last_record_size_;
  }

  // The page offset of the most recent record read by TryAdvance().
  //
  // Must only be called after a successful call to TryAdvance().
  int64_t last_record_offset() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(last_record_size_ != 0)
        << "TryAdvance() not called / did not succeed";
    return last_record_offset_;
  }

  // Returns true iff TryAdvance() may be called.
  bool CanAdvance() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return next_read_index_ < cell_count_;
  }

  // Advances the reader and returns the last read value.
  //
  // Returns false if the read fails. The caller should continue attempting to
  // read as long as CanAdvance() returns true.
  bool TryAdvance();

  // True if the given reader may point to an inner page in a table B-tree.
  //
  // The last ReadPage() call on |db_reader| must have succeeded.
  static bool IsOnValidPage(DatabasePageReader* db_reader);

 private:
  // Returns the number of cells in the B-tree page.
  //
  // Checks for database corruption. The caller can assume that the cell pointer
  // array with the returned size will not extend past the page buffer.
  static int ComputeCellCount(DatabasePageReader* db_reader);

  // The number of the B-tree page this reader is reading.
  const int64_t page_id_;
  // Used to read the tree page.
  //
  // Raw pointer usage is acceptable because this instance's owner is expected
  // to ensure that the DatabasePageReader outlives this.
  // This field is not a raw_ptr<> because it caused a
  // std::is_trivially_destructible static_assert failure.
  RAW_PTR_EXCLUSION DatabasePageReader* const db_reader_;
  // Caches the ComputeCellCount() value for this reader's page.
  const int cell_count_ = ComputeCellCount(db_reader_);

  // The reader's cursor state.
  //
  // Each B-tree cell contains a value. So, this member takes values in
  // [0, cell_count_).
  int next_read_index_ = 0;

  int64_t last_record_size_ = 0;
  int64_t last_record_rowid_ = 0;
  int last_record_offset_ = 0;

  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace recover
}  // namespace sql

#endif  // SQL_RECOVER_MODULE_BTREE_H_