From b14f60fd682de238af57e3c1019818b3e19f3c0f Mon Sep 17 00:00:00 2001 From: Kawamura Shintaro Date: Sat, 28 Oct 2023 15:56:37 +0900 Subject: [PATCH] Extract btree::compute_free_size() 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. --- src/btree.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/cursor.rs | 69 ++++++++++++++++++-------------------- 2 files changed, 124 insertions(+), 37 deletions(-) diff --git a/src/btree.rs b/src/btree.rs index 40bacfd..9921df4 100644 --- a/src/btree.rs +++ b/src/btree.rs @@ -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 { @@ -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 { + 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::*; @@ -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); + } } diff --git a/src/cursor.rs b/src/cursor.rs index 9ee0486..83516dd 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -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; @@ -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; @@ -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: // @@ -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. @@ -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;