Skip to content

Commit

Permalink
Extract btree::compute_free_size()
Browse files Browse the repository at this point in the history
Several page header fields are retreived duplicatedly with this change.
This will not be a problem when we cache the free size in a page later.
  • Loading branch information
kawasin73 committed Oct 28, 2023
1 parent bda326c commit b14f60f
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 37 deletions.
92 changes: 92 additions & 0 deletions src/btree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub fn set_u16(buf: &mut [u8], offset: usize, value: u16) {

pub const BTREE_OVERFLOW_PAGE_ID_BYTES: usize = 4;

#[derive(Debug, Clone, Copy)]
pub struct BtreePageType(u8);

impl BtreePageType {
Expand Down Expand Up @@ -623,6 +624,37 @@ impl<'a, 'page> CellWriter<'a, 'page> {
}
}

/// Compute the free size of the page.
///
/// n_cells is an argument because this is cached in cursor.
pub fn compute_free_size(page: &MemPage, buffer: &PageBufferMut, n_cells: u16) -> ParseResult<u16> {
let page_header = BtreePageHeader::from_page_mut(page, buffer);
let header_size = page_header.page_type().header_size();
let first_freeblock_offset = page_header.first_freeblock_offset();
let cell_content_area_offset = page_header.cell_content_area_offset().get() as usize;
let fragmented_free_bytes = page_header.fragmented_free_bytes();
let unallocated_space_offset = cell_pointer_offset(page, n_cells, header_size);

if cell_content_area_offset < unallocated_space_offset {
return Err("invalid cell content area offset");
}

// unallocated_size must fit in u16 because cell_content_area_offset is at most
// 65536 and unallocated_space_offset must be bigger than 0.
let unallocated_size = (cell_content_area_offset - unallocated_space_offset) as u16;

let mut free_size = unallocated_size;
for (freeblock_offset, size) in FreeblockIterator::new(first_freeblock_offset, buffer) {
if freeblock_offset < cell_content_area_offset {
return Err("invalid freeblock offset");
}
free_size += size;
}
free_size += fragmented_free_bytes as u16;

Ok(free_size)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -1008,4 +1040,64 @@ mod tests {
);
assert_eq!(&buffer[2700..2800], &[2; 100]);
}

#[test]
fn test_compute_free_size() {
let pager = create_empty_pager(&[], 2 * 4096);
let page_type = BtreePageType(BTREE_PAGE_TYPE_LEAF_TABLE);

let (page_id, page) = pager.allocate_page().unwrap();
assert_eq!(page_id, 1);
let mut buffer = pager.make_page_mut(&page).unwrap();
let mut page_header = BtreePageHeaderMut::from_page(&page, &mut buffer);
page_header.set_page_type(page_type);
page_header.set_cell_content_area_offset(4096);
page_header.set_first_freeblock_offset(0);
page_header.clear_fragmented_free_bytes();
assert_eq!(compute_free_size(&page, &buffer, 0).unwrap(), 3988);

let mut page_header = BtreePageHeaderMut::from_page(&page, &mut buffer);
page_header.set_cell_content_area_offset(4000);
page_header.add_fragmented_free_bytes(3);
assert_eq!(compute_free_size(&page, &buffer, 0).unwrap(), 3895);
assert_eq!(compute_free_size(&page, &buffer, 10).unwrap(), 3875);

let mut page_header = BtreePageHeaderMut::from_page(&page, &mut buffer);
page_header.set_cell_content_area_offset(2000);
page_header.set_first_freeblock_offset(3000);
// freeblock 3000 ~ 3010
set_u16(&mut buffer, 3000, 3100);
set_u16(&mut buffer, 3002, 10);
// freeblock 3100 ~ 3200
set_u16(&mut buffer, 3100, 0);
set_u16(&mut buffer, 3102, 100);
assert_eq!(compute_free_size(&page, &buffer, 10).unwrap(), 1985);

let (page_id, page) = pager.allocate_page().unwrap();
assert_ne!(page_id, 1);
let mut buffer = pager.make_page_mut(&page).unwrap();
let mut page_header = BtreePageHeaderMut::from_page(&page, &mut buffer);
page_header.set_page_type(page_type);
page_header.set_cell_content_area_offset(4096);
page_header.set_first_freeblock_offset(0);
page_header.clear_fragmented_free_bytes();
assert_eq!(compute_free_size(&page, &buffer, 0).unwrap(), 4088);

let mut page_header = BtreePageHeaderMut::from_page(&page, &mut buffer);
page_header.set_cell_content_area_offset(4000);
page_header.add_fragmented_free_bytes(3);
assert_eq!(compute_free_size(&page, &buffer, 0).unwrap(), 3995);
assert_eq!(compute_free_size(&page, &buffer, 10).unwrap(), 3975);

let mut page_header = BtreePageHeaderMut::from_page(&page, &mut buffer);
page_header.set_cell_content_area_offset(2000);
page_header.set_first_freeblock_offset(3000);
// freeblock 3000 ~ 3010
set_u16(&mut buffer, 3000, 3100);
set_u16(&mut buffer, 3002, 10);
// freeblock 3100 ~ 3200
set_u16(&mut buffer, 3100, 0);
set_u16(&mut buffer, 3102, 100);
assert_eq!(compute_free_size(&page, &buffer, 10).unwrap(), 2085);
}
}
69 changes: 32 additions & 37 deletions src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use anyhow::bail;

use crate::btree::allocate_from_freeblocks;
use crate::btree::cell_pointer_offset;
use crate::btree::compute_free_size;
use crate::btree::get_cell_offset;
use crate::btree::parse_btree_interior_cell_page_id;
use crate::btree::parse_btree_leaf_table_cell;
Expand All @@ -27,7 +28,6 @@ use crate::btree::BtreePageHeader;
use crate::btree::BtreePageHeaderMut;
use crate::btree::BtreePageType;
use crate::btree::CellWriter;
use crate::btree::FreeblockIterator;
use crate::btree::IndexCellKeyParser;
use crate::btree::PayloadInfo;
use crate::btree::TableCellKeyParser;
Expand Down Expand Up @@ -462,43 +462,9 @@ impl<'a> BtreeCursor<'a> {
todo!("update the payload");
}
_ => {
let buffer = self.current_page.mem.buffer();
let page_header = BtreePageHeader::from_page(&self.current_page.mem, &buffer);
let page_type = page_header.page_type();
assert!(page_type.is_table());
assert!(page_type.is_leaf());

let header_size = page_type.header_size();
let unallocated_space_offset = cell_pointer_offset(
&self.current_page.mem,
self.current_page.n_cells,
header_size,
);
assert!(unallocated_space_offset > 0);
let cell_content_area_offset =
page_header.cell_content_area_offset().get() as usize;
if cell_content_area_offset < unallocated_space_offset {
bail!("invalid cell content area offset");
}

let unallocated_size = (cell_content_area_offset - unallocated_space_offset) as u16;
let first_freeblock_offset = page_header.first_freeblock_offset();
let fragmented_free_bytes = page_header.fragmented_free_bytes();

// TODO: Cache free size.
let mut free_size = unallocated_size;
for (_, size) in FreeblockIterator::new(first_freeblock_offset, &buffer) {
free_size += size;
}
free_size += fragmented_free_bytes as u16;

if free_size < cell_size + 2 {
// TODO: balance the btree.
todo!("balance the btree");
}
assert!(self.current_page.page_type.is_table());
assert!(self.current_page.page_type.is_leaf());

// Upgrade the buffer to writable.
drop(buffer);
// Upgrading should be success because there must be no buffer reference of the
// page. We can guarantee it because:
//
Expand All @@ -508,6 +474,34 @@ impl<'a> BtreeCursor<'a> {
// which is mutable method.
let mut buffer = self.pager.make_page_mut(&self.current_page.mem)?;

// TODO: Cache free size.
let free_size =
compute_free_size(&self.current_page.mem, &buffer, self.current_page.n_cells)
.map_err(|e| anyhow::anyhow!("compute free size: {:?}", e))?;

if free_size < cell_size + 2 {
// TODO: balance the btree.
todo!("balance the btree");
}

let page_header = BtreePageHeader::from_page_mut(&self.current_page.mem, &buffer);
let page_type = self.current_page.page_type;
let header_size = page_type.header_size();
let first_freeblock_offset = page_header.first_freeblock_offset();
let cell_content_area_offset =
page_header.cell_content_area_offset().get() as usize;
let unallocated_space_offset = cell_pointer_offset(
&self.current_page.mem,
self.current_page.n_cells,
header_size,
);
let fragmented_free_bytes = page_header.fragmented_free_bytes();
// unallocated_size must fit in u16 because cell_content_area_offset is at most
// 65536 and unallocated_space_offset must be bigger than 0.
// cell_content_area_offset < unallocated_space_offset is asserted in
// compute_free_size().
let unallocated_size = (cell_content_area_offset - unallocated_space_offset) as u16;

// Allocate space
//
// 1. Search freeblock first.
Expand Down Expand Up @@ -799,6 +793,7 @@ impl<'a> BtreeCursor<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::btree::FreeblockIterator;
use crate::header::DATABASE_HEADER_SIZE;
use crate::pager::MAX_PAGE_SIZE;
use crate::record::parse_record;
Expand Down

0 comments on commit b14f60f

Please sign in to comment.