// 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_PAYLOAD_H_
#define SQL_RECOVER_MODULE_PAYLOAD_H_

#include <cstdint>
#include <ostream>
#include <vector>

#include "base/check_op.h"
#include "base/memory/raw_ptr.h"
#include "base/sequence_checker.h"
#include "sql/recover_module/pager.h"

namespace sql {
namespace recover {

class DatabasePageReader;

// Reads payloads (records) across B-tree pages and overflow pages.
//
// Instances are designed to be reused for reading multiple payloads. Instances
// are not thread-safe.
//
// Reading a payload is started by calling Initialize() with the information
// from LeafPageDecoder. If the call succeeds, ReadPayload() can be called
// repeatedly.
class LeafPayloadReader {
 public:
  // Number of payload bytes guaranteed to be on the B-tree page.
  //
  // The value is derived from the minimum SQLite usable page size, which is
  // 380 bytes, and the formula for the minimum payload size given a usable page
  // size.
  static constexpr int kMinInlineSize = ((380 - 12) * 32) / 255 - 23;

  explicit LeafPayloadReader(DatabasePageReader* db_reader);
  ~LeafPayloadReader();

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

  // Sets up the reader for a new payload.
  //
  // The DatabasePageReader passed to the constructor must be focused on the
  // page containing the payload.
  //
  // This method must complete successfully before any other method on this
  // class can be called.
  bool Initialize(int64_t payload_size, int payload_offset);

  // True if the last call to Initialize succeeded.
  bool IsInitialized() const;

  // The number of payload bytes that are stored on the B-tree page.
  //
  // The return value is guaranteed to be non-negative and at most
  // payload_size().
  int inline_payload_size() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(page_id_ != DatabasePageReader::kInvalidPageId)
        << "Initialize() not called, or last call did not succeed";
    DCHECK_LE(inline_payload_size_, payload_size_);
    return inline_payload_size_;
  }

  // Total payload size, in bytes.
  //
  // This includes the bytes stored in the B-tree page, as well as any bytes
  // stored in overflow pages.
  //
  // The return value is guaranteed to be at least inline_payload_size().
  int payload_size() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(page_id_ != DatabasePageReader::kInvalidPageId)
        << "Initialize() not called, or last call did not succeed";
    DCHECK_LE(inline_payload_size_, payload_size_);
    return payload_size_;
  }

  // Copies a subset of the payload into a given buffer.
  //
  // Returns true if the read succeeds.
  //
  // May only be called after a previous call to Initialize() that returns true.
  bool ReadPayload(int64_t offset, int64_t size, uint8_t* buffer);

  // Pulls the B-tree containing the payload into the database reader's cache.
  //
  // Returns a pointer to the beginning of the payload bytes. The pointer is
  // inside the database reader's buffer, and may get invalidated if the
  // database reader is used.
  //
  // Returns null if the read operation fails.
  //
  // May only be called after a previous call to Initialize() that returns true.
  const uint8_t* ReadInlinePayload();

 private:
  // Extends the cached list of overflow page IDs by one page.
  //
  // Returns false if the operation failed. Failures are due to read errors or
  // database corruption.
  bool PopulateNextOverflowPageId();

  // Used to read the pages containing the payload.
  //
  // Raw pointer usage is acceptable because this instance's owner is expected
  // to ensure that the DatabasePageReader outlives this.
  const raw_ptr<DatabasePageReader> db_reader_;

  // Total size of the current payload.
  int64_t payload_size_ = 0;

  // The ID of the B-tree page containing the current payload's inline bytes.
  //
  // Set to kInvalidPageId if the reader wasn't successfully initialized.
  int page_id_ = DatabasePageReader::kInvalidPageId;

  // The start of the current payload's inline bytes on the B-tree page.
  //
  // Large payloads extend past the B-tree page containing the payload, via
  // overflow pages.
  int inline_payload_offset_ = 0;

  // Number of bytes in the current payload stored in its B-tree page.
  //
  // The rest of the payload is stored on overflow pages.
  int inline_payload_size_ = 0;

  // Number of overflow pages used by the payload.
  int overflow_page_count_ = 0;

  // Number of bytes in each overflow page that stores the payload.
  int max_overflow_payload_size_ = 0;

  // Page IDs for all the payload's overflow pages, in order.
  //
  // This list is populated on-demand.
  std::vector<int> overflow_page_ids_;

  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace recover
}  // namespace sql

#endif  // SQL_RECOVER_MODULE_PAYLOAD_H_