// 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_RECORD_H_
#define SQL_RECOVER_MODULE_RECORD_H_

#include <cstdint>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/sequence_checker.h"

struct sqlite3_context;

namespace sql {
namespace recover {

// The effective type of a column's value in a SQLite row.
enum class ValueType {
  kNull,
  kInteger,
  kFloat,
  kText,
  kBlob,
};

class LeafPayloadReader;

// Reads records from SQLite B-trees.
//
// Instances are designed to be reused for reading multiple records. Instances
// are not thread-safe.
//
// A record is a list of column values. SQLite uses "manifest typing", meaning
// that values don't necessarily match the column types declared in the
// table/index schema.
//
// Reading a record is started by calling Initialize(). Afterwards,
// GetValueType() can be used to validate the types of the record's values, and
// ReadValue() can be used to read the values into a SQLite user-defined
// function context.
class RecordReader {
 public:
  struct ValueHeader {
    explicit ValueHeader(int64_t offset,
                         int64_t size,
                         ValueType type,
                         int8_t inline_value,
                         bool has_inline_value)
        : offset(offset),
          size(size),
          type(type),
          inline_value(inline_value),
          has_inline_value(has_inline_value) {}

    // The position of the first byte used to encode the value, in the record.
    int64_t offset;
    // The number of bytes used to encode the value.
    int64_t size;
    // The SQLite type for the value.
    ValueType type;
    // The value encoded directly in the type, if |has_inline_value| is true.
    int8_t inline_value;
    // True if |inline_value| is defined.
    bool has_inline_value;
  };

  // Creates an uninitialized record reader from a SQLite table B-tree.
  //
  // |payload_reader_| must outlive this instance, and should always point to
  // leaf pages in the same tree. |column_count| must match the number of
  // columns in the table's schema.
  //
  // The underlying table should not be modified while the record is
  // initialized.
  explicit RecordReader(LeafPayloadReader* payload_reader_, int column_count);
  ~RecordReader();

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

  // Sets up the reader for a new payload.
  //
  // The LeafPayloadReader 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();

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

  // The type of a value in the record. |column_index| is 0-based.
  ValueType GetValueType(int column_index) const;

  // Reads a value in the record into a SQLite user-defined function context.
  //
  // |column_index| is 0-based.
  //
  // The value is reported by calling a sqlite3_result_*() function on
  // |receiver|. SQLite's result-reporting API is documented at
  // https://www.sqlite.org/c3ref/result_blob.html
  //
  // Returns false if the reading value fails. This can happen if a value is
  // stored across overflow pages, and reading one of the overflow pages results
  // in an I/O error.
  bool ReadValue(int column_index, sqlite3_context* receiver) const;

  // Resets the reader.
  //
  // This method is idempotent. After it is called, IsInitialized() will return
  // false.
  void Reset();

 private:
  // Reads the record's header into |header_buffer_|.
  //
  // Returns the size of the record's header, or 0 (zero) in case of failure.
  // No valid record header has 0 bytes, because the record header includes at
  // least one varint.
  //
  // On success, |header_buffer_|'s size will be set correctly.
  int64_t InitializeHeaderBuffer();

  // Stores decoded type IDs from the record's header.
  std::vector<ValueHeader> value_headers_;

  // Stores the header of the record being read.
  //
  // The header is only used during Initialize(). This buffer is reused across
  // multiple Initialize() calls to reduce heap churn.
  std::vector<uint8_t> header_buffer_;

  // Brings the record's bytes from the SQLite database pages.
  //
  // Raw pointer usage is acceptable because this instance's owner is expected
  // to ensure that the LeafPayloadReader outlives this.
  const raw_ptr<LeafPayloadReader> payload_reader_;

  // The number of columns in the table schema. No payload should have more than
  // this number of columns.
  const int column_count_;

  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace recover
}  // namespace sql

#endif  // SQL_RECOVER_MODULE_RECORD_H_