| // 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_PAGER_H_ |
| #define SQL_RECOVER_MODULE_PAGER_H_ |
| |
| #include <cstdint> |
| #include <memory> |
| #include <ostream> |
| |
| #include "base/check_op.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/sequence_checker.h" |
| |
| struct sqlite3_file; |
| |
| namespace sql { |
| namespace recover { |
| |
| class VirtualTable; |
| |
| // Page reader for SQLite database files. |
| // |
| // Contains logic for retrying reads on I/O errors. Caches the last read page, |
| // to facilitate layering in higher-level code. |
| // |
| // Instances should be members of high-level constructs such as tables or |
| // cursors. Instances are not thread-safe. |
| class DatabasePageReader { |
| public: |
| // Guaranteed to be an invalid page number. NB: use `IsValidPageId()` to |
| // validate a page id. |
| static constexpr int kHighestInvalidPageId = 0; |
| |
| // Minimum database page size supported by SQLite. |
| static constexpr int kMinPageSize = 512; |
| // Maximum database page size supported by SQLite. |
| static constexpr int kMaxPageSize = 65536; |
| |
| // The size of the header at the beginning of a SQLite database file. |
| static constexpr int kDatabaseHeaderSize = 100; |
| |
| // Minimum usable size of a SQLite database page. |
| // |
| // This differs from |kMinPageSize| because the first page in a SQLite |
| // database starts with the database header. That page's header starts right |
| // after the database header. |
| static constexpr int kMinUsablePageSize = kMinPageSize - kDatabaseHeaderSize; |
| |
| // Largest valid page ID in a SQLite database. |
| // |
| // This is the maximum value of SQLITE_MAX_PAGE_COUNT plus 1, because page IDs |
| // start at 1. The numerical value, which is the same as |
| // std::numeric_limits<int32_t>::max() - 1, is quoted from |
| // https://www.sqlite.org/limits.html. |
| static constexpr int kMaxPageId = 2147483646 + 1; |
| |
| // Creates a reader that uses the SQLite VFS backing |table|. |
| // |
| // |table| must outlive this instance. |
| explicit DatabasePageReader(VirtualTable* table); |
| ~DatabasePageReader(); |
| |
| DatabasePageReader(const DatabasePageReader&) = delete; |
| DatabasePageReader& operator=(const DatabasePageReader&) = delete; |
| |
| // The page data read by the last ReadPage() call. |
| // |
| // The page data is undefined if the last ReadPage() call failed, or if |
| // ReadPage() was never called. |
| const uint8_t* page_data() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(IsValidPageId(page_id_)) |
| << "Successful ReadPage() required before accessing pager state"; |
| return page_data_.get(); |
| } |
| |
| // The number of bytes in the page read by the last ReadPage() call. |
| // |
| // The result is guaranteed to be in [kMinUsablePageSize, kMaxPageSize]. |
| // |
| // In general, pages have the same size. However, the first page in each |
| // database is smaller, because it starts after the database header. |
| // |
| // The result is undefined if the last ReadPage() call failed, or if |
| // ReadPage() was never called. |
| int page_size() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(IsValidPageId(page_id_)) |
| << "Successful ReadPage() required before accessing pager state"; |
| CHECK_GE(page_size_, kMinUsablePageSize); |
| CHECK_LE(page_size_, kMaxPageSize); |
| return page_size_; |
| } |
| |
| // Returns the |page_id| argument for the last successful ReadPage() call. |
| // |
| // The result is undefined if the last ReadPage() call failed, or if |
| // ReadPage() was never called. |
| int page_id() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(IsValidPageId(page_id_)) |
| << "Successful ReadPage() required before accessing pager state"; |
| return page_id_; |
| } |
| |
| // Reads a database page. Returns a SQLite status code. |
| // |
| // SQLite uses 1-based indexing for its page numbers. |
| // |
| // This method is idempotent, because it caches its result. |
| int ReadPage(int page_id); |
| |
| // True if the given database page size is supported by SQLite. |
| static constexpr bool IsValidPageSize(int page_size) noexcept { |
| // SQLite page sizes must be powers of two. |
| return page_size >= kMinPageSize && page_size <= kMaxPageSize && |
| (page_size & (page_size - 1)) == 0; |
| } |
| |
| // True if the given number is a valid SQLite database page ID. |
| // |
| // Valid page IDs are positive 32-bit integers. |
| static constexpr bool IsValidPageId(int64_t page_id) noexcept { |
| return page_id > kHighestInvalidPageId && page_id <= kMaxPageId; |
| } |
| |
| // Low-level read wrapper. Returns a SQLite error code. |
| // |
| // |read_size| and |read_offset| are expressed in bytes. |
| static int RawRead(sqlite3_file* sqlite_file, |
| int read_size, |
| int64_t read_offset, |
| uint8_t* buffer); |
| |
| private: |
| // Points to the last page successfully read by ReadPage(). |
| // Set to kHighestInvalidPageId if the last read was unsuccessful. |
| int page_id_ = kHighestInvalidPageId; |
| // Stores the bytes of the last page successfully read by ReadPage(). |
| // The content is undefined if the last call to ReadPage() did not succeed. |
| const std::unique_ptr<uint8_t[]> page_data_; |
| // Raw pointer usage is acceptable because this instance's owner is expected |
| // to ensure that the VirtualTable outlives this. |
| const raw_ptr<VirtualTable> table_; |
| int page_size_ = 0; |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| }; |
| |
| } // namespace recover |
| } // namespace sql |
| |
| #endif // SQL_RECOVER_MODULE_PAGER_H_ |