diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 678ef646e..7f76f9d46 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -85,7 +85,10 @@ struct PageStack { /// cell_indices[current_page] is the current cell index being consumed. Similarly /// cell_indices[current_page-1] is the cell index of the parent of the current page /// that we save in case of going back up. - cell_indices: RefCell<[usize; BTCURSOR_MAX_DEPTH + 1]>, + /// There are two points that need special attention: + /// If cell_indices[current_page] = -1, it indicates that the current iteration has reached the start of the current_page + /// If cell_indices[current_page] = `cell_count`, it means that the current iteration has reached the end of the current_page + cell_indices: RefCell<[i32; BTCURSOR_MAX_DEPTH + 1]>, } impl BTreeCursor { @@ -117,10 +120,6 @@ impl BTreeCursor { } } - fn cell_count(&self) -> Result> { - self.load_page_with(|page| Ok(CursorResult::Ok(page.cell_count()))) - } - fn is_empty_table(&mut self) -> Result> { let page = self.pager.read_page(self.root_page)?; let page = RefCell::borrow(&page); @@ -132,104 +131,86 @@ impl BTreeCursor { Ok(CursorResult::Ok(cell_count == 0)) } - fn get_cell_with( - &self, - handle: impl Fn(BTreeCell) -> Result>, - ) -> Result> { - let mem_page_rc = self.stack.top(); - let cell_idx = self.stack.current_index(); - - debug!("current id={} cell={}", mem_page_rc.borrow().id, cell_idx); - if mem_page_rc.borrow().is_locked() { - return Ok(CursorResult::IO); - } - if !mem_page_rc.borrow().is_loaded() { - self.pager.load_page(mem_page_rc.clone())?; - return Ok(CursorResult::IO); - } - let mem_page = mem_page_rc.borrow(); - - let contents = mem_page.contents.as_ref().unwrap(); - - let cell = contents.cell_get( - cell_idx, - self.pager.clone(), - self.max_local(contents.page_type()), - self.min_local(contents.page_type()), - self.usable_space(), - )?; - - handle(cell) - } - - fn move_to_child_rightmost(&mut self) -> Result> { - // let mem_page = self.get_mem_page(); - - // let handle_cell = |cell: BTreeCell| match cell { - // BTreeCell::TableInteriorCell(TableInteriorCell { - // _left_child_page, - // _rowid, - // }) => { - // let cell_count = self.cell_count(_left_child_page as usize)?; - // match cell_count { - // CursorResult::Ok(cell_count) => { - // self.page.replace(Some(Rc::new(MemPage::new( - // Some(mem_page.clone()), - // _left_child_page as usize, - // cell_count - 1, - // )))); - // Ok(CursorResult::Ok(())) - // } - // CursorResult::IO => Ok(CursorResult::IO), - // } - // } - // _ => todo!(), - // }; - - // self.get_cell_with(handle_cell) - Ok(CursorResult::Ok(())) - } - - fn btree_prev(&mut self) -> Result> { - // let mut mem_page = self.get_mem_page(); - // loop { - // if mem_page.cell_idx() > 0 { - // mem_page.retreat(); - // let page_idx = mem_page.page_idx; - // self.page.replace(Some(mem_page)); - // match self.load_page_with(page_idx, |page| Ok(CursorResult::Ok(page.is_leaf())))? { - // CursorResult::Ok(true) => return Ok(CursorResult::Ok(())), - // CursorResult::Ok(false) => return self.move_to_child_rightmost(), - // CursorResult::IO => return Ok(CursorResult::IO), - // }; - // } - // match &mem_page.parent { - // Some(parent) => mem_page = parent.clone(), - // None => { - // // moved to start of btree - // self.page.replace(None); - // return Ok(CursorResult::Ok(())); - // } - // } - // } - Ok(CursorResult::Ok(())) - } - fn get_prev_record(&mut self) -> Result, Option)>> { - let handle_cell = |cell: BTreeCell| match cell { - BTreeCell::TableLeafCell(TableLeafCell { - _rowid, _payload, .. - }) => { - let record: OwnedRecord = crate::storage::sqlite3_ondisk::read_record(&_payload)?; - Ok(CursorResult::Ok((Some(_rowid), Some(record)))) - } - BTreeCell::IndexLeafCell(_) => todo!(), - _ => unreachable!(), - }; + loop { + let mem_page_rc = self.stack.top(); + let cell_idx = self.stack.current_index(); - let record = self.get_cell_with(handle_cell)?; - self.btree_prev()?; - Ok(record) + // moved to current page begin + // todo: find a better way to flag moved to end or begin of page + if cell_idx < 0 { + loop { + if self.stack.current_index() > 0 { + self.stack.retreat(); + break; + } + if self.stack.has_parent() { + self.stack.pop(); + } else { + // moved to begin of btree + return Ok(CursorResult::Ok((None, None))); + } + } + // continue to next loop to get record from the new page + continue; + } + + let cell_idx = cell_idx as usize; + debug!("current id={} cell={}", mem_page_rc.borrow().id, cell_idx); + if mem_page_rc.borrow().is_locked() { + return Ok(CursorResult::IO); + } + if !mem_page_rc.borrow().is_loaded() { + self.pager.load_page(mem_page_rc.clone())?; + return Ok(CursorResult::IO); + } + let mem_page = mem_page_rc.borrow(); + let contents = mem_page.contents.as_ref().unwrap(); + + let cell_count = contents.cell_count(); + let cell_idx = if cell_idx >= cell_count { + self.stack.set_cell_index(cell_count as i32 - 1); + cell_count - 1 + } else { + cell_idx + }; + + let cell = contents.cell_get( + cell_idx, + self.pager.clone(), + self.max_local(contents.page_type()), + self.min_local(contents.page_type()), + self.usable_space(), + )?; + + match cell { + BTreeCell::TableInteriorCell(TableInteriorCell { + _left_child_page, + _rowid, + }) => { + let mem_page = self.pager.read_page(_left_child_page as usize)?; + let cell_count = mem_page.borrow().contents.as_ref().unwrap().cell_count(); + if cell_count == 0 { + // leaf page is empty, load next leaf page + self.stack.retreat(); + } else { + self.stack.push(mem_page); + self.stack.set_cell_index(cell_count as i32 - 1); + } + continue; + } + BTreeCell::TableLeafCell(TableLeafCell { + _rowid, _payload, .. + }) => { + self.stack.retreat(); + let record: OwnedRecord = + crate::storage::sqlite3_ondisk::read_record(&_payload)?; + return Ok(CursorResult::Ok((Some(_rowid), Some(record)))); + } + BTreeCell::IndexInteriorCell(_) => todo!(), + BTreeCell::IndexLeafCell(_) => todo!(), + } + } } fn get_next_record( @@ -238,7 +219,7 @@ impl BTreeCursor { ) -> Result, Option)>> { loop { let mem_page_rc = self.stack.top(); - let cell_idx = self.stack.current_index(); + let cell_idx = self.stack.current_index() as usize; debug!("current id={} cell={}", mem_page_rc.borrow().id, cell_idx); if mem_page_rc.borrow().is_locked() { @@ -508,14 +489,14 @@ impl BTreeCursor { let contents = page.contents.as_ref().unwrap(); if contents.is_leaf() { if contents.cell_count() > 0 { - self.stack.set_cell_index(contents.cell_count() - 1); + self.stack.set_cell_index(contents.cell_count() as i32 - 1); } return Ok(CursorResult::Ok(())); } match contents.rightmost_pointer() { Some(right_most_pointer) => { - self.stack.set_cell_index(contents.cell_count() + 1); + self.stack.set_cell_index(contents.cell_count() as i32 + 1); let mem_page = self.pager.read_page(right_most_pointer as usize).unwrap(); self.stack.push(mem_page); continue; @@ -831,39 +812,6 @@ impl BTreeCursor { page.write_u16(BTREE_HEADER_OFFSET_CELL_COUNT, page.cell_count() as u16 - 1); } - fn load_page_with( - &self, - handle: impl Fn(&PageContent) -> Result>, - ) -> Result> { - let mem_page_rc = self.stack.top(); - let cell_idx = self.stack.current_index(); - - debug!("current id={} cell={}", mem_page_rc.borrow().id, cell_idx); - if mem_page_rc.borrow().is_locked() { - return Ok(CursorResult::IO); - } - if !mem_page_rc.borrow().is_loaded() { - self.pager.load_page(mem_page_rc.clone())?; - return Ok(CursorResult::IO); - } - let mem_page = mem_page_rc.borrow(); - - let contents = mem_page.contents.as_ref().unwrap(); - - handle(contents) - } - - // fn get_page(&mut self) -> crate::Result>> { - // let mem_page = { - // let mem_page = self.page.borrow(); - // let mem_page = mem_page.as_ref().unwrap(); - // mem_page.clone() - // }; - // let page_idx = mem_page.page_idx; - // let page_ref = self.pager.read_page(page_idx)?; - // Ok(page_ref) - // } - /// This is a naive algorithm that doesn't try to distribute cells evenly by content. /// It will try to split the page in half by keys not by content. /// Sqlite tries to have a page at least 40% full. @@ -1618,7 +1566,7 @@ impl PageStack { } /// Cell index of the current page - fn current_index(&self) -> usize { + fn current_index(&self) -> i32 { let current = self.current(); self.cell_indices.borrow()[current] } @@ -1629,9 +1577,14 @@ impl PageStack { self.cell_indices.borrow_mut()[current] += 1; } - fn set_cell_index(&self, idx: usize) { + fn retreat(&self) { let current = self.current(); - self.cell_indices.borrow_mut()[current] = idx; + self.cell_indices.borrow_mut()[current] -= 1; + } + + fn set_cell_index(&self, idx: i32) { + let current = self.current(); + self.cell_indices.borrow_mut()[current] = idx } fn has_parent(&self) -> bool { diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index a7a5ecf32..b1ba2d2bf 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -8,7 +8,7 @@ use sqlite3_parser::ast; use crate::schema::{Column, PseudoTable, Table}; use crate::storage::sqlite3_ondisk::DatabaseHeader; use crate::translate::expr::resolve_ident_pseudo_table; -use crate::translate::plan::Search; +use crate::translate::plan::{IterationDirection, Search}; use crate::types::{OwnedRecord, OwnedValue}; use crate::vdbe::builder::ProgramBuilder; use crate::vdbe::{BranchOffset, Insn, Program}; @@ -173,13 +173,15 @@ impl Emitter for Operator { id, step, predicates, - reverse, + iter_dir, } => { *step += 1; const SCAN_OPEN_READ: usize = 1; const SCAN_BODY: usize = 2; const SCAN_NEXT: usize = 3; - let reverse = reverse.is_some_and(|r| r); + let reverse = iter_dir + .as_ref() + .is_some_and(|iter_dir| *iter_dir == IterationDirection::Backwards); match *step { SCAN_OPEN_READ => { let cursor_id = program.alloc_cursor_id( diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index dc62ac3e6..833943fe3 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -6,7 +6,7 @@ use crate::{schema::Index, util::normalize_ident, Result}; use super::plan::{ get_table_ref_bitmask_for_ast_expr, get_table_ref_bitmask_for_operator, BTreeTableReference, - Direction, Operator, Plan, ProjectionColumn, Search, + Direction, IterationDirection, Operator, Plan, ProjectionColumn, Search, }; /** @@ -89,7 +89,7 @@ fn eliminate_unnecessary_orderby( match operator { Operator::Order { source, key, .. } => { if key.len() != 1 { - // TODO: handle multiple order by keys and descending order + // TODO: handle multiple order by keys return Ok(()); } @@ -577,11 +577,11 @@ fn push_predicate( fn push_scan_direction(operator: &mut Operator, direction: &Direction) { match operator { Operator::Projection { source, .. } => push_scan_direction(source, direction), - Operator::Scan { reverse, .. } => { - if reverse.is_none() { + Operator::Scan { iter_dir, .. } => { + if iter_dir.is_none() { match direction { - Direction::Ascending => *reverse = Some(false), - Direction::Descending => *reverse = Some(true), + Direction::Ascending => *iter_dir = Some(IterationDirection::Forwards), + Direction::Descending => *iter_dir = Some(IterationDirection::Backwards), } } } diff --git a/core/translate/plan.rs b/core/translate/plan.rs index fc508a726..2de8c1f3c 100644 --- a/core/translate/plan.rs +++ b/core/translate/plan.rs @@ -26,6 +26,12 @@ impl Display for Plan { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum IterationDirection { + Forwards, + Backwards, +} + /** An Operator is a Node in the query plan. Operators form a tree structure, with each having zero or more children. @@ -114,7 +120,7 @@ pub enum Operator { table_reference: BTreeTableReference, predicates: Option>, step: usize, - reverse: Option, + iter_dir: Option, }, // Search operator // This operator is used to search for a row in a table using an index diff --git a/core/translate/planner.rs b/core/translate/planner.rs index 38c5b2b40..0a7fac1e2 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -319,7 +319,7 @@ fn parse_from( predicates: None, id: operator_id_counter.get_next_id(), step: 0, - reverse: None, + iter_dir: None, }; let mut tables = vec![first_table]; @@ -399,7 +399,7 @@ fn parse_join( predicates: None, id: operator_id_counter.get_next_id(), step: 0, - reverse: None, + iter_dir: None, }, outer, predicates, diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index ac37d8ea2..67c975fb6 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -885,7 +885,7 @@ fn sqlite3_errstr_impl(rc: i32) -> *const std::ffi::c_char { "datatype mismatch", // SQLITE_MISMATCH "bad parameter or other API misuse", // SQLITE_MISUSE #[cfg(feature = "lfs")] - "", // SQLITE_NOLFS + "", // SQLITE_NOLFS #[cfg(not(feature = "lfs"))] "large file support is disabled", // SQLITE_NOLFS "authorization denied", // SQLITE_AUTH