init 2
This commit is contained in:
parent
063631e05f
commit
e72438b886
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ Cargo.lock
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
db.sqlite
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "build-my-own-sqlite"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.94"
|
||||
bytes = "1.9.0"
|
||||
thiserror = "2.0.4"
|
322
src/header.rs
Normal file
322
src/header.rs
Normal file
@ -0,0 +1,322 @@
|
||||
use std::io::Read;
|
||||
use bytes::Buf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum HeaderParseError {
|
||||
#[error("Not enough data")]
|
||||
NotEnoughData,
|
||||
#[error("Invalid magic header")]
|
||||
InvalidMagicHeader,
|
||||
#[error("Invalid page size")]
|
||||
InvalidPageSize,
|
||||
#[error("Invalid file format version")]
|
||||
InvalidFileFormatVersion,
|
||||
#[error("Invalid text encoding")]
|
||||
InvalidTextEncoding,
|
||||
#[error("Invalid maximum embedded payload fraction")]
|
||||
InvalidMaximumEmbeddedPayloadFraction,
|
||||
#[error("Invalid minimum embedded payload fraction")]
|
||||
InvalidMinimumEmbeddedPayloadFraction,
|
||||
#[error("Invalid leaf payload fraction")]
|
||||
InvalidLeafPayloadFraction,
|
||||
}
|
||||
|
||||
const MAGIC_HEADER: &str = "SQLite format 3\0";
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SqliteFileFormatVersion {
|
||||
Legacy = 1,
|
||||
WAL = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum SqliteTextEncoding {
|
||||
Utf8 = 1,
|
||||
Utf16le = 2,
|
||||
Utf16be = 3,
|
||||
}
|
||||
|
||||
const MAX_EMBEDDED_PAYLOAD_FRACTION: u8 = 64;
|
||||
const MIN_EMBEDDED_PAYLOAD_FRACTION: u8 = 32;
|
||||
const LEAF_PAYLOAD_FRACTION: u8 = 32;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Version {
|
||||
major: u8,
|
||||
minor: u8,
|
||||
patch: u8,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
||||
}
|
||||
}
|
||||
|
||||
impl Version {
|
||||
pub fn from_sqlite_version_number(mut version_number: u32) -> Self {
|
||||
let major = (version_number / 1000000) as u8;
|
||||
version_number %= 1000000;
|
||||
let minor = (version_number / 1000) as u8;
|
||||
version_number %= 1000;
|
||||
let patch = version_number as u8;
|
||||
Version {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SqliteHeader {
|
||||
/**
|
||||
* The database page size in bytes. Must be a power of two between 512 and 65536 inclusive.
|
||||
*/
|
||||
pub page_size: u32,
|
||||
/*
|
||||
* The file format write version. 1 for legacy, 2 for WAL.
|
||||
*/
|
||||
pub file_format_write_version: SqliteFileFormatVersion,
|
||||
pub file_format_read_version: SqliteFileFormatVersion,
|
||||
/**
|
||||
* The reserved space at the end of each page. Usually 0.
|
||||
*/
|
||||
pub reserved_space: u8,
|
||||
/**
|
||||
* file change counter.
|
||||
*/
|
||||
pub file_change_counter: u32,
|
||||
/**
|
||||
* The size of the database in pages. The "in-header database size" is the number of pages in the database according to the most recent commit.
|
||||
* This might be different from the "on-disk database size" if the database has been growing and the size of the database file has not yet been updated.
|
||||
*/
|
||||
pub database_size: u32,
|
||||
/**
|
||||
* The page number of the first freelist trunk page.
|
||||
*/
|
||||
pub first_freelist_trunk_page: u32,
|
||||
/**
|
||||
* The total number of freelist pages.
|
||||
*/
|
||||
pub total_freelist_pages: u32,
|
||||
/**
|
||||
* The schema cookie.
|
||||
*/
|
||||
pub schema_cookie: u32,
|
||||
/**
|
||||
* The schema format number.
|
||||
* New database files created by SQLite use format 4 by default.
|
||||
* If the database is completely empty, then the schema format number is 0.
|
||||
*/
|
||||
pub schema_format_number: u32,
|
||||
/**
|
||||
* The default page cache size in pages. The value is a suggestion only.
|
||||
*/
|
||||
pub default_page_cache_size: u32,
|
||||
|
||||
pub largest_root_b_tree_page_number: u32,
|
||||
/**
|
||||
* The database text encoding. 1 for UTF-8, 2 for UTF-16le, 3 for UTF-16be.
|
||||
*/
|
||||
pub text_encoding: SqliteTextEncoding,
|
||||
/**
|
||||
* The user version.
|
||||
* sqlite3 does not use this value internally. It is available for use by applications.
|
||||
*/
|
||||
pub user_version: u32,
|
||||
|
||||
pub incremental_vacuum_mode: u32,
|
||||
/**
|
||||
* The application ID.
|
||||
* sqlite3 does not use this value internally. It is available for use by applications.
|
||||
*/
|
||||
pub application_id: u32,
|
||||
/**
|
||||
* The version-valid-for number.
|
||||
*/
|
||||
pub version_valid_for: u32,
|
||||
/**
|
||||
* The SQLite version number.
|
||||
*/
|
||||
pub sqlite_version_number: Version,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SqliteFileFormatVersion {
|
||||
type Error = HeaderParseError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
1 => Ok(SqliteFileFormatVersion::Legacy),
|
||||
2 => Ok(SqliteFileFormatVersion::WAL),
|
||||
_ => Err(HeaderParseError::InvalidFileFormatVersion),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for SqliteTextEncoding {
|
||||
type Error = HeaderParseError;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
1 => Ok(SqliteTextEncoding::Utf8),
|
||||
2 => Ok(SqliteTextEncoding::Utf16le),
|
||||
3 => Ok(SqliteTextEncoding::Utf16be),
|
||||
_ => Err(HeaderParseError::InvalidTextEncoding),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SqliteHeader {
|
||||
fn parse_page_size(page_size: u16) -> Result<u32, HeaderParseError> {
|
||||
if page_size == 1 {
|
||||
Ok(65536)
|
||||
} else {
|
||||
if page_size < 512 || page_size > 32768 {
|
||||
Err(HeaderParseError::InvalidPageSize)
|
||||
}
|
||||
else {
|
||||
Ok(page_size as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn parse_maximum_embedded_payload_fraction(fraction: u8) -> Result<u8, HeaderParseError> {
|
||||
if (fraction < MIN_EMBEDDED_PAYLOAD_FRACTION) || (fraction > MAX_EMBEDDED_PAYLOAD_FRACTION) {
|
||||
Err(HeaderParseError::InvalidMaximumEmbeddedPayloadFraction)
|
||||
}
|
||||
else {
|
||||
Ok(fraction)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_minimum_embedded_payload_fraction(fraction: u8) -> Result<u8, HeaderParseError> {
|
||||
if (fraction < MIN_EMBEDDED_PAYLOAD_FRACTION) || (fraction > MAX_EMBEDDED_PAYLOAD_FRACTION) {
|
||||
Err(HeaderParseError::InvalidMinimumEmbeddedPayloadFraction)
|
||||
}
|
||||
else {
|
||||
Ok(fraction)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_leaf_payload_fraction(fraction: u8) -> Result<u8, HeaderParseError> {
|
||||
if (fraction < LEAF_PAYLOAD_FRACTION) || (fraction > MAX_EMBEDDED_PAYLOAD_FRACTION) {
|
||||
Err(HeaderParseError::InvalidLeafPayloadFraction)
|
||||
}
|
||||
else {
|
||||
Ok(fraction)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_from_reader<R: Read>(reader: &mut R) -> Result<Self, HeaderParseError> {
|
||||
let mut buffer = [0u8; 100];
|
||||
reader.read_exact(&mut buffer).map_err(|_| HeaderParseError::NotEnoughData)?;
|
||||
Self::read(&buffer)
|
||||
}
|
||||
|
||||
pub fn read(buffer: &[u8]) -> Result<Self, HeaderParseError> {
|
||||
let mut reader = buffer;
|
||||
// check file size
|
||||
if buffer.len() < 100 {
|
||||
return Err(HeaderParseError::NotEnoughData);
|
||||
}
|
||||
// check magic header
|
||||
let magic_header = &reader.copy_to_bytes(MAGIC_HEADER.len());
|
||||
let magic_header = std::str::from_utf8(&magic_header).map_err(
|
||||
|_| HeaderParseError::InvalidMagicHeader
|
||||
)?;
|
||||
|
||||
if magic_header != MAGIC_HEADER {
|
||||
return Err(HeaderParseError::InvalidMagicHeader);
|
||||
}
|
||||
|
||||
let page_size = SqliteHeader::parse_page_size(reader.get_u16())?;
|
||||
let file_format_write_version = SqliteFileFormatVersion::try_from(reader.get_u8())?;
|
||||
let file_format_read_version = SqliteFileFormatVersion::try_from(reader.get_u8())?;
|
||||
let reserved_space = reader.get_u8();
|
||||
let maximum_embedded_payload_fraction = reader.get_u8();
|
||||
SqliteHeader::parse_maximum_embedded_payload_fraction(maximum_embedded_payload_fraction)?;
|
||||
let minimum_embedded_payload_fraction = reader.get_u8();
|
||||
SqliteHeader::parse_minimum_embedded_payload_fraction(minimum_embedded_payload_fraction)?;
|
||||
let leaf_payload_fraction = reader.get_u8();
|
||||
SqliteHeader::parse_leaf_payload_fraction(leaf_payload_fraction)?;
|
||||
|
||||
let file_change_counter = reader.get_u32();
|
||||
let database_size = reader.get_u32();
|
||||
let first_freelist_trunk_page = reader.get_u32();
|
||||
let total_freelist_pages = reader.get_u32();
|
||||
let schema_cookie = reader.get_u32();
|
||||
let schema_format_number = reader.get_u32();
|
||||
let default_page_cache_size = reader.get_u32();
|
||||
let largest_root_b_tree_page_number = reader.get_u32();
|
||||
let text_encoding = SqliteTextEncoding::try_from(reader.get_u32())?;
|
||||
let user_version = reader.get_u32();
|
||||
let incremental_vacuum_mode = reader.get_u32();
|
||||
let application_id = reader.get_u32();
|
||||
// reserved space
|
||||
reader.advance(20);
|
||||
let version_valid_for = reader.get_u32();
|
||||
let sqlite_version_number = reader.get_u32();
|
||||
let sqlite_version_number = Version::from_sqlite_version_number(sqlite_version_number);
|
||||
|
||||
Ok(Self {
|
||||
page_size,
|
||||
file_format_write_version,
|
||||
file_format_read_version,
|
||||
reserved_space,
|
||||
file_change_counter,
|
||||
database_size,
|
||||
first_freelist_trunk_page,
|
||||
total_freelist_pages,
|
||||
schema_cookie,
|
||||
schema_format_number,
|
||||
default_page_cache_size,
|
||||
largest_root_b_tree_page_number,
|
||||
text_encoding,
|
||||
user_version,
|
||||
incremental_vacuum_mode,
|
||||
application_id,
|
||||
version_valid_for,
|
||||
sqlite_version_number,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_version_from_sqlite_version_number() {
|
||||
use super::Version;
|
||||
assert_eq!(Version::from_sqlite_version_number(3008000).to_string(), "3.8.0");
|
||||
assert_eq!(Version::from_sqlite_version_number(3010000).to_string(), "3.10.0");
|
||||
assert_eq!(Version::from_sqlite_version_number(3011000).to_string(), "3.11.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_sqlite_page_size() {
|
||||
use super::SqliteHeader;
|
||||
assert_eq!(SqliteHeader::parse_page_size(1).unwrap(), 65536);
|
||||
assert_eq!(SqliteHeader::parse_page_size(512).unwrap(), 512);
|
||||
assert_eq!(SqliteHeader::parse_page_size(32768).unwrap(), 32768);
|
||||
assert!(SqliteHeader::parse_page_size(511).is_err());
|
||||
assert!(SqliteHeader::parse_page_size(32769).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_maximum_embedded_payload_fraction() {
|
||||
use super::SqliteHeader;
|
||||
assert_eq!(SqliteHeader::parse_maximum_embedded_payload_fraction(32).unwrap(), 32);
|
||||
assert_eq!(SqliteHeader::parse_maximum_embedded_payload_fraction(64).unwrap(), 64);
|
||||
assert!(SqliteHeader::parse_maximum_embedded_payload_fraction(31).is_err());
|
||||
assert!(SqliteHeader::parse_maximum_embedded_payload_fraction(65).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_minimum_embedded_payload_fraction() {
|
||||
use super::SqliteHeader;
|
||||
assert_eq!(SqliteHeader::parse_minimum_embedded_payload_fraction(32).unwrap(), 32);
|
||||
assert_eq!(SqliteHeader::parse_minimum_embedded_payload_fraction(64).unwrap(), 64);
|
||||
assert!(SqliteHeader::parse_minimum_embedded_payload_fraction(31).is_err());
|
||||
assert!(SqliteHeader::parse_minimum_embedded_payload_fraction(65).is_err());
|
||||
}
|
||||
}
|
60
src/main.rs
Normal file
60
src/main.rs
Normal file
@ -0,0 +1,60 @@
|
||||
mod header;
|
||||
mod page;
|
||||
|
||||
use std::{fs::File, io::{Read, Seek}};
|
||||
use bytes::Buf;
|
||||
|
||||
use header::SqliteHeader;
|
||||
use page::{varint_read_from_buf, BTreePage, Value};
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let mut f = File::options().read(true).write(true).open("db.sqlite")?;
|
||||
let header = SqliteHeader::read_from_reader(& mut f)?;
|
||||
println!("{:?}", header);
|
||||
// print page size
|
||||
println!("Page size: {}", header.page_size);
|
||||
|
||||
let mut buf = vec![0; header.page_size as usize];
|
||||
f.seek(std::io::SeekFrom::Start(0))?;
|
||||
f.read_exact(&mut buf)?;
|
||||
let btr = BTreePage::read(&buf, true)?;
|
||||
// println!("{:?}", btr);
|
||||
let mut table_count = 0;
|
||||
match btr {
|
||||
BTreePage::LeafNode(leaf) => {
|
||||
println!("{:?}", leaf);
|
||||
leaf.data_cells.iter().for_each(|cell| {
|
||||
let buf = &cell.data;
|
||||
let mut buf = buf.as_slice();
|
||||
|
||||
let (mut column_size, i) = varint_read_from_buf(&mut buf);
|
||||
let mut serial_types = vec![];
|
||||
column_size -= i as u64;
|
||||
while column_size > 0 {
|
||||
let (serial_type, i) = varint_read_from_buf(&mut buf);
|
||||
serial_types.push(serial_type);
|
||||
column_size -= i as u64;
|
||||
}
|
||||
let values = serial_types.iter().map(|&serial_type| {
|
||||
let v = Value::from_bytes(&mut buf, serial_type);
|
||||
println!("{:?}", v);
|
||||
v
|
||||
}).collect::<Vec<_>>();
|
||||
println!("{:?}", values);
|
||||
if let Some(v) = values.first() {
|
||||
if let Value::Text(s) = v {
|
||||
if s == "table" {
|
||||
table_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
BTreePage::InternalNode(internal) => {
|
||||
println!("{:?}", internal);
|
||||
}
|
||||
}
|
||||
println!("Table count: {}", table_count);
|
||||
// get
|
||||
Ok(())
|
||||
}
|
344
src/page.rs
Normal file
344
src/page.rs
Normal file
@ -0,0 +1,344 @@
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use bytes::{buf, Buf};
|
||||
use thiserror::Error;
|
||||
|
||||
struct FreeTrunkPage {
|
||||
/**
|
||||
* The next trunk page in the list of free pages.
|
||||
* If this is the last trunk page in the list, this field is 0.
|
||||
*/
|
||||
next_trunk_page: u32,
|
||||
/**
|
||||
* vector of free list leaf pages in this trunk page.
|
||||
*/
|
||||
free_list: Vec<u32>,
|
||||
// A bug in SQLite versions prior to 3.6.0 (2008-07-16) caused the database to be reported as corrupt
|
||||
// if any of the last 6 entries in the freelist trunk page array contained non-zero values.
|
||||
// Newer versions of SQLite do not have this problem. However, newer versions of SQLite still avoid
|
||||
// using the last six entries in the freelist trunk page array in order that database files created
|
||||
// by newer versions of SQLite can be read by older versions of SQLite.
|
||||
}
|
||||
|
||||
impl FreeTrunkPage {
|
||||
pub fn read_from_reader<R: std::io::Read>(reader: &mut R, page_size: u32) -> Result<Self, Error> {
|
||||
let mut buf = vec![0; page_size as usize];
|
||||
reader.read_exact(&mut buf)?;
|
||||
let mut buf = buf.as_slice();
|
||||
let next_trunk_page = buf.get_u32();
|
||||
let count = buf.get_u32();
|
||||
let mut free_list = Vec::new();
|
||||
for _ in 0..count {
|
||||
free_list.push(buf.get_u32());
|
||||
}
|
||||
Ok(FreeTrunkPage {
|
||||
next_trunk_page,
|
||||
free_list,
|
||||
})
|
||||
}
|
||||
pub fn get_next_trunk_page(&self) -> u32 {
|
||||
self.next_trunk_page
|
||||
}
|
||||
pub fn get_free_list(&self) -> &Vec<u32> {
|
||||
&self.free_list
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A free list leaf page.
|
||||
* Should contain no information.
|
||||
*/
|
||||
struct FreeLeapPage;
|
||||
|
||||
/**
|
||||
* B-tree page.
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub enum BTreePage {
|
||||
/**
|
||||
* Internal node.
|
||||
*/
|
||||
InternalNode(InternalNode),
|
||||
/**
|
||||
* Leaf node.
|
||||
*/
|
||||
LeafNode(LeafNode),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NodeType {
|
||||
Table,
|
||||
Index,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InternalNode {
|
||||
node_type: NodeType,
|
||||
|
||||
/**
|
||||
* start of the cells on the page
|
||||
*/
|
||||
first_free_cell: u16,
|
||||
/**
|
||||
* number of cells on the page
|
||||
*/
|
||||
cell_count: u16,
|
||||
/**
|
||||
* cell content area start
|
||||
*/
|
||||
data_start: u16,
|
||||
/**
|
||||
* the number of fragmented free bytes within the cell content area.
|
||||
*/
|
||||
fragment_count: u8,
|
||||
last_child_cell: u32,
|
||||
|
||||
child_cells: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LeafNode {
|
||||
node_type: NodeType,
|
||||
/**
|
||||
* start of the cells on the page
|
||||
*/
|
||||
first_free_cell: u16,
|
||||
/**
|
||||
* number of cells on the page
|
||||
*/
|
||||
cell_count: u16,
|
||||
/**
|
||||
* cell content area start
|
||||
*/
|
||||
data_start: u16,
|
||||
/**
|
||||
* the number of fragmented free bytes within the cell content area.
|
||||
*/
|
||||
fragment_count: u8,
|
||||
|
||||
pub data_cells: Vec<DataCell>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataCell {
|
||||
pub row_id: u64,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
Null,
|
||||
Integer(i64),
|
||||
Real(f64),
|
||||
Text(String),
|
||||
Blob(Vec<u8>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn from_bytes(data: &mut &[u8], serial_type: u64) -> Value {
|
||||
let buf = data;
|
||||
match serial_type {
|
||||
0 => Value::Null,
|
||||
1 => Value::Integer(buf.get_u8() as i64),
|
||||
2 => Value::Integer(buf.get_u16() as i64),
|
||||
3 => Value::Integer({
|
||||
let mut value = 0;
|
||||
for i in 0..3 {
|
||||
value |= (buf.get_u8() as i64) << (i * 8);
|
||||
}
|
||||
value
|
||||
} as i64),
|
||||
4 => Value::Integer(buf.get_u32() as i64),
|
||||
5 => Value::Integer({
|
||||
let mut value = 0;
|
||||
for i in 0..6 {
|
||||
value |= (buf.get_u8() as i64) << (i * 8);
|
||||
}
|
||||
value
|
||||
} as i64),
|
||||
6 => Value::Integer(buf.get_u64() as i64),
|
||||
7 => Value::Real(buf.get_f64()),
|
||||
8 => Value::Integer(0),
|
||||
9 => Value::Integer(1),
|
||||
// Reserved for internal use.
|
||||
10|11 => panic!("Invalid serial type: {}", serial_type),
|
||||
st if st >= 12 && st % 2 == 0 => {
|
||||
let len = (st - 12) / 2;
|
||||
let blob = buf[0..len as usize].to_vec();
|
||||
buf.advance(len as usize);
|
||||
Value::Blob(blob)
|
||||
}
|
||||
st if st >= 13 && st % 2 == 1 => {
|
||||
let len = (st - 13) / 2;
|
||||
let text = buf[0..len as usize].to_vec();
|
||||
buf.advance(len as usize);
|
||||
// println!("text: {:?} {:?}", text, len);
|
||||
Value::Text(String::from_utf8(text).unwrap())
|
||||
},
|
||||
_ => panic!("Invalid serial type: {}", serial_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Invalid page type: {0}")]
|
||||
InvalidPageType(u8),
|
||||
#[error("Invalid page size: {0}")]
|
||||
InvalidPageSize(u32),
|
||||
#[error("Invalid cell count: {0}")]
|
||||
InvalidCellCount(u16),
|
||||
#[error("Invalid right child page: {0}")]
|
||||
InvalidRightChildPage(u32),
|
||||
#[error("Invalid child page: {0}")]
|
||||
InvalidChildPage(u32),
|
||||
#[error("Invalid cell pointer: {0}")]
|
||||
InvalidCellPointer(u16),
|
||||
}
|
||||
|
||||
impl BTreePage {
|
||||
pub fn read_from_reader<R: Read + Seek> (
|
||||
reader: &mut R,
|
||||
page_size: u32,
|
||||
) -> Result<BTreePage, Error> {
|
||||
let mut buf = vec![0; page_size as usize];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Self::read(&buf, false)
|
||||
}
|
||||
pub fn read(
|
||||
buffer: &[u8],
|
||||
is_first_page: bool,
|
||||
) -> Result<BTreePage, Error> {
|
||||
let mut buf = buffer;
|
||||
if is_first_page {
|
||||
// skip the header
|
||||
buf = &buffer[100..];
|
||||
}
|
||||
let page_type = buf.get_u8();
|
||||
let first_free_cell = buf.get_u16();
|
||||
let cell_count = buf.get_u16();
|
||||
let data_start = buf.get_u16();
|
||||
let fragment_count = buf.get_u8();
|
||||
|
||||
match page_type {
|
||||
2 | 5 => {
|
||||
let last_child_cell = if page_type == 2 {
|
||||
buf.get_u32()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let mut cell_pointers = Vec::new();
|
||||
for _ in 0..cell_count {
|
||||
cell_pointers.push(buf.get_u16());
|
||||
}
|
||||
|
||||
let mut child = Vec::new();
|
||||
for offset in cell_pointers {
|
||||
let offset = offset as u32;
|
||||
let offset = offset;
|
||||
buf = &buffer[offset as usize..];
|
||||
let child_page = buf.get_u32();
|
||||
child.push(child_page);
|
||||
}
|
||||
|
||||
Ok(BTreePage::InternalNode(InternalNode {
|
||||
cell_count,
|
||||
first_free_cell,
|
||||
data_start,
|
||||
fragment_count,
|
||||
last_child_cell,
|
||||
child_cells: child,
|
||||
node_type: if page_type == 2 { NodeType::Table } else { NodeType::Index },
|
||||
}))
|
||||
}
|
||||
10 | 13 => {
|
||||
// buf.get_u32();
|
||||
let mut cell_pointers = Vec::new();
|
||||
for _ in 0..cell_count {
|
||||
cell_pointers.push(buf.get_u16());
|
||||
}
|
||||
// println!("cell_pointers: {:?}", cell_pointers);
|
||||
let mut data_cells = Vec::new();
|
||||
for offset in cell_pointers {
|
||||
println!("offset: {:#02x}", offset);
|
||||
let offset = offset as u32;
|
||||
let offset = offset;
|
||||
buf = &buffer[(offset) as usize..];
|
||||
|
||||
let (data_len, _) = varint_read_from_buf(&mut buf);
|
||||
let (row_id, _) = varint_read_from_buf(&mut buf);
|
||||
println!("data_len: {}, row_id: {}", data_len, row_id);
|
||||
let data = buf[..data_len as usize].to_vec();
|
||||
data_cells.push(DataCell {
|
||||
row_id,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Ok(BTreePage::LeafNode(LeafNode {
|
||||
node_type: if page_type == 10 { NodeType::Table } else { NodeType::Index },
|
||||
cell_count,
|
||||
first_free_cell,
|
||||
data_start,
|
||||
fragment_count,
|
||||
data_cells,
|
||||
}))
|
||||
}
|
||||
_ => Err(Error::InvalidPageType(page_type)),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn varint_read_from_buf(buf: &mut &[u8]) -> (u64, u32) {
|
||||
let mut byte = buf.get_u8();
|
||||
let mut value: u64 = (byte & 0x7F) as u64;
|
||||
let mut i = 1;
|
||||
while byte & 0x80 != 0 && i < 9 {
|
||||
byte = buf.get_u8();
|
||||
value <<= 7;
|
||||
value |= (byte & 0x7F) as u64;
|
||||
i += 1;
|
||||
}
|
||||
if i == 8 && byte & 0x80 != 0 {
|
||||
byte = buf.get_u8();
|
||||
value <<= 8;
|
||||
value |= byte as u64;
|
||||
i += 1;
|
||||
};
|
||||
(value, i)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_varint_read_from_buf() {
|
||||
let buf = [0x82, 0x00];
|
||||
let mut buf = buf.as_ref();
|
||||
let (value, i) = super::varint_read_from_buf(&mut buf);
|
||||
assert_eq!(value, 0x100);
|
||||
assert_eq!(i, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_varint_read_from_buf_2() {
|
||||
let buf = [0x82, 0x01];
|
||||
let mut buf = buf.as_ref();
|
||||
let (value, i) = super::varint_read_from_buf(&mut buf);
|
||||
assert_eq!(value, 0x101);
|
||||
assert_eq!(i, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_varint_read_from_buf_3() {
|
||||
let buf = [0x81, 0x91, 0xd1, 0xac, 0x78];
|
||||
let mut buf = buf.as_ref();
|
||||
let (value, i) = super::varint_read_from_buf(&mut buf);
|
||||
assert_eq!(value, 0x12345678);
|
||||
assert_eq!(i, 5);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user