#include "sql/recover_module/btree.h"
#include <algorithm>
#include <limits>
#include <ostream>
#include <type_traits>
#include "base/check_op.h"
#include "sql/recover_module/integers.h"
#include "sql/recover_module/pager.h"
#include "third_party/sqlite/sqlite3.h"
namespace sql {
namespace recover {
namespace {
constexpr uint8_t kInnerTablePageType = 0x05;
constexpr uint8_t kLeafTablePageType = 0x0D;
constexpr int kPageTypePageOffset = 0;
constexpr int kCellCountPageOffset = 3;
constexpr int kLastChildIdInnerPageOffset = 8;
constexpr int kFirstCellOfsetInnerPageOffset = 12;
constexpr int kFirstCellOfsetLeafPageOffset = 8;
}
#if !DCHECK_IS_ON()
static_assert(std::is_trivially_destructible<InnerPageDecoder>::value,
"Move the destructor to the .cc file if it's non-trival");
#endif
InnerPageDecoder::InnerPageDecoder(DatabasePageReader* db_reader) noexcept
: page_id_(db_reader->page_id()),
db_reader_(db_reader),
cell_count_(ComputeCellCount(db_reader)),
next_read_index_(0) {
DCHECK(IsOnValidPage(db_reader));
DCHECK(DatabasePageReader::IsValidPageId(page_id_));
}
int InnerPageDecoder::TryAdvance() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(CanAdvance());
const int sqlite_status = db_reader_->ReadPage(page_id_);
if (sqlite_status != SQLITE_OK) {
next_read_index_ = cell_count_ + 1;
return DatabasePageReader::kInvalidPageId;
}
const uint8_t* const page_data = db_reader_->page_data();
const int read_index = next_read_index_;
next_read_index_ += 1;
if (read_index == cell_count_)
return LoadBigEndianInt32(page_data + kLastChildIdInnerPageOffset);
const int cell_pointer_offset =
kFirstCellOfsetInnerPageOffset + (read_index << 1);
DCHECK_LE(cell_pointer_offset + 2, db_reader_->page_size())
<< "ComputeCellCount() used an incorrect upper bound";
const int cell_pointer = LoadBigEndianUint16(page_data + cell_pointer_offset);
static_assert(std::numeric_limits<uint16_t>::max() + 4 <
std::numeric_limits<int>::max(),
"The addition below may overflow");
if (cell_pointer + 4 >= db_reader_->page_size()) {
return DatabasePageReader::kInvalidPageId;
}
if (cell_pointer < kFirstCellOfsetInnerPageOffset) {
return DatabasePageReader::kInvalidPageId;
}
return LoadBigEndianInt32(page_data + cell_pointer);
}
bool InnerPageDecoder::IsOnValidPage(DatabasePageReader* db_reader) {
static_assert(kPageTypePageOffset < DatabasePageReader::kMinUsablePageSize,
"The check below may perform an out-of-bounds memory access");
return db_reader->page_data()[kPageTypePageOffset] == kInnerTablePageType;
}
int InnerPageDecoder::ComputeCellCount(DatabasePageReader* db_reader) {
int header_count =
LoadBigEndianUint16(db_reader->page_data() + kCellCountPageOffset);
static_assert(
kCellCountPageOffset + 2 <= DatabasePageReader::kMinUsablePageSize,
"The read above may be out of bounds");
DCHECK((db_reader->page_size() - kFirstCellOfsetInnerPageOffset) % 2 == 0);
int upper_bound =
(db_reader->page_size() - kFirstCellOfsetInnerPageOffset) >> 1;
static_assert(
kFirstCellOfsetInnerPageOffset <= DatabasePageReader::kMinUsablePageSize,
"The |upper_bound| computation above may overflow");
return std::min(header_count, upper_bound);
}
#if !DCHECK_IS_ON()
static_assert(std::is_trivially_destructible<LeafPageDecoder>::value,
"Move the destructor to the .cc file if it's non-trival");
#endif
LeafPageDecoder::LeafPageDecoder(DatabasePageReader* db_reader) noexcept
: page_id_(db_reader->page_id()),
db_reader_(db_reader),
cell_count_(ComputeCellCount(db_reader)),
next_read_index_(0),
last_record_size_(0) {
DCHECK(IsOnValidPage(db_reader));
DCHECK(DatabasePageReader::IsValidPageId(page_id_));
}
bool LeafPageDecoder::TryAdvance() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(CanAdvance());
#if DCHECK_IS_ON()
last_record_size_ = 0;
#endif
const int sqlite_status = db_reader_->ReadPage(page_id_);
if (sqlite_status != SQLITE_OK) {
next_read_index_ = cell_count_;
return false;
}
const uint8_t* page_data = db_reader_->page_data();
const int read_index = next_read_index_;
next_read_index_ += 1;
const int cell_pointer_offset =
kFirstCellOfsetLeafPageOffset + (read_index << 1);
DCHECK_LE(cell_pointer_offset + 2, db_reader_->page_size())
<< "ComputeCellCount() used an incorrect upper bound";
const int cell_pointer = LoadBigEndianUint16(page_data + cell_pointer_offset);
static_assert(std::numeric_limits<uint16_t>::max() + 3 <
std::numeric_limits<int>::max(),
"The addition below may overflow");
if (cell_pointer + 3 >= db_reader_->page_size()) {
return false;
}
if (cell_pointer < kFirstCellOfsetLeafPageOffset) {
return false;
}
const uint8_t* const cell_start = page_data + cell_pointer;
const uint8_t* const page_end = page_data + db_reader_->page_size();
DCHECK_LT(cell_start, page_end) << "Failed to skip over empty cells";
const uint8_t* rowid_start;
std::tie(last_record_size_, rowid_start) = ParseVarint(cell_start, page_end);
if (rowid_start == page_end) {
return false;
}
if (last_record_size_ <= 0) {
#if DCHECK_IS_ON()
last_record_size_ = 0;
#endif
return false;
}
const uint8_t* record_start;
std::tie(last_record_rowid_, record_start) =
ParseVarint(rowid_start, page_end);
if (record_start == page_end) {
last_record_size_ = 0;
return false;
}
last_record_offset_ = record_start - page_data;
return true;
}
bool LeafPageDecoder::IsOnValidPage(DatabasePageReader* db_reader) {
static_assert(kPageTypePageOffset < DatabasePageReader::kMinUsablePageSize,
"The check below may perform an out-of-bounds memory access");
return db_reader->page_data()[kPageTypePageOffset] == kLeafTablePageType;
}
int LeafPageDecoder::ComputeCellCount(DatabasePageReader* db_reader) {
int header_count =
LoadBigEndianUint16(db_reader->page_data() + kCellCountPageOffset);
static_assert(
kCellCountPageOffset + 2 <= DatabasePageReader::kMinUsablePageSize,
"The read above may be out of bounds");
int upper_bound =
(db_reader->page_size() - kFirstCellOfsetLeafPageOffset) >> 1;
static_assert(
kFirstCellOfsetLeafPageOffset <= DatabasePageReader::kMinUsablePageSize,
"The |upper_bound| computation above may overflow");
return std::min(header_count, upper_bound);
}
}
}