From 113c55fb3e0b322fc9049302ac5f9f14ba87044a Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Tue, 21 Jan 2025 22:42:22 -0300 Subject: [PATCH 01/17] wip: basic ephemeralcursor impl --- core/ephemeral.rs | 17 +++++++++++++++++ core/lib.rs | 1 + core/schema.rs | 16 +++++++++++++++- core/vdbe/builder.rs | 3 ++- core/vdbe/mod.rs | 8 ++++++++ 5 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 core/ephemeral.rs diff --git a/core/ephemeral.rs b/core/ephemeral.rs new file mode 100644 index 000000000..e6757ce63 --- /dev/null +++ b/core/ephemeral.rs @@ -0,0 +1,17 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::schema::EphemeralTable; + +pub struct EphemeralCursor { + table: Rc>, + rowid: Option, +} + +impl EphemeralCursor { + pub fn new() -> Self { + Self { + table: todo!(), + rowid: todo!(), + } + } +} diff --git a/core/lib.rs b/core/lib.rs index 8fde24402..dd54b6b9e 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -1,3 +1,4 @@ +mod ephemeral; mod error; mod ext; mod function; diff --git a/core/schema.rs b/core/schema.rs index f4a6aee2b..d445f1a77 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -1,3 +1,4 @@ +use crate::types::OwnedValue; use crate::VirtualTable; use crate::{util::normalize_ident, Result}; use core::fmt; @@ -8,7 +9,7 @@ use sqlite3_parser::{ ast::{Cmd, CreateTableBody, QualifiedName, ResultColumn, Stmt}, lexer::sql::Parser, }; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::rc::Rc; pub struct Schema { @@ -49,6 +50,7 @@ pub enum Table { BTree(Rc), Pseudo(Rc), Virtual(Rc), + EphemeralTable(Rc), } impl Table { @@ -57,6 +59,7 @@ impl Table { Table::BTree(table) => table.root_page, Table::Pseudo(_) => unimplemented!(), Table::Virtual(_) => unimplemented!(), + Self::EphemeralTable(_) => todo!(), } } @@ -65,6 +68,7 @@ impl Table { Self::BTree(table) => &table.name, Self::Pseudo(_) => "", Self::Virtual(table) => &table.name, + Self::EphemeralTable(_) => "ephemeral_table", } } @@ -73,6 +77,7 @@ impl Table { Self::BTree(table) => table.columns.get(index), Self::Pseudo(table) => table.columns.get(index), Self::Virtual(table) => table.columns.get(index), + Self::EphemeralTable(table) => table.columns.get(index), } } @@ -81,6 +86,7 @@ impl Table { Self::BTree(table) => &table.columns, Self::Pseudo(table) => &table.columns, Self::Virtual(table) => &table.columns, + Self::EphemeralTable(table) => &table.columns, } } @@ -89,6 +95,7 @@ impl Table { Self::BTree(table) => Some(table.clone()), Self::Pseudo(_) => None, Self::Virtual(_) => None, + Self::EphemeralTable(_) => None, } } @@ -940,3 +947,10 @@ mod tests { Ok(()) } } + +#[derive(Debug)] +pub struct EphemeralTable { + rows: BTreeMap>, // rowid -> each index in the Vec is a column + next_rowid: u64, // generate rowids + columns: Vec, // columns +} diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index c3ead0d38..d8250be73 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ parameters::Parameters, - schema::{BTreeTable, Index, PseudoTable}, + schema::{BTreeTable, EphemeralTable, Index, PseudoTable}, storage::sqlite3_ondisk::DatabaseHeader, translate::plan::{ResultSetColumn, TableReference}, Connection, VirtualTable, @@ -39,6 +39,7 @@ pub enum CursorType { BTreeTable(Rc), BTreeIndex(Rc), Pseudo(Rc), + Ephemeral(Rc), Sorter, VirtualTable(Rc), } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 5003b72c6..0defd874a 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -23,6 +23,7 @@ pub mod insn; pub mod likeop; pub mod sorter; +use crate::ephemeral::EphemeralCursor; use crate::error::{LimboError, SQLITE_CONSTRAINT_PRIMARYKEY}; use crate::ext::ExtValue; use crate::function::{AggFunc, ExtFunc, FuncCtx, MathFunc, MathFuncArity, ScalarFunc, VectorFunc}; @@ -340,6 +341,7 @@ impl ProgramState { cursors, registers, result_row: None, + ephemeral_cursors, last_compare: None, deferred_seek: None, ended_coroutine: Bitfield::new(), @@ -395,6 +397,7 @@ macro_rules! must_be_btree_cursor { CursorType::BTreeTable(_) => get_cursor_as_table_mut(&mut $cursors, $cursor_id), CursorType::BTreeIndex(_) => get_cursor_as_index_mut(&mut $cursors, $cursor_id), CursorType::Pseudo(_) => panic!("{} on pseudo cursor", $insn_name), + CursorType::Ephemeral(_) => panic!("{} on ephemeral cursor", $insn_name), CursorType::Sorter => panic!("{} on sorter cursor", $insn_name), CursorType::VirtualTable(_) => panic!("{} on virtual table cursor", $insn_name), }; @@ -847,6 +850,10 @@ impl Program { .unwrap() .replace(Cursor::new_index(cursor)); } + CursorType::Ephemeral(_) => { + let cursor = EphemeralCursor::new(); + ephemeral_cursors.insert(*cursor_id, cursor); + } CursorType::Pseudo(_) => { panic!("OpenReadAsync on pseudo cursor"); } @@ -1042,6 +1049,7 @@ impl Program { "Insn::Column on virtual table cursor, use Insn::VColumn instead" ); } + CursorType::Ephemeral(ephemeral_table) => todo!(), } state.pc += 1; From 2f1c66e7f91dbc780aa5d5a0fa6329e55f089e99 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 22 Jan 2025 11:22:07 -0300 Subject: [PATCH 02/17] wip: add EphemeralCursor to must_be_btree_cursor macro --- core/ephemeral.rs | 62 +++++++++++++++++++++++++++++++++++++++--- core/schema.rs | 16 +++++++++-- core/vdbe/explain.rs | 18 +++++++++++- core/vdbe/insn.rs | 6 ++++ core/vdbe/mod.rs | 65 +++++++++++++++++++++++++++++++++----------- 5 files changed, 143 insertions(+), 24 deletions(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index e6757ce63..f6a47860b 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -1,17 +1,71 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Ref, RefCell}, + rc::Rc, +}; -use crate::schema::EphemeralTable; +use crate::{ + schema::EphemeralTable, + types::{CursorResult, OwnedRecord}, + LimboError, +}; +use crate::{types::OwnedValue, Result}; pub struct EphemeralCursor { table: Rc>, rowid: Option, + current: Option, + null_flag: bool, } impl EphemeralCursor { pub fn new() -> Self { + let table = Rc::new(RefCell::new(EphemeralTable::new())); Self { - table: todo!(), - rowid: todo!(), + table, + rowid: None, + current: None, + null_flag: false, } } + + pub fn rewind(&mut self) -> Result> { + todo!() + } + pub fn last(&mut self) -> Result> { + todo!() + } + + pub fn wait_for_completion(&mut self) -> Result<()> { + // TODO: Wait for pager I/O to complete + Ok(()) + } + + pub fn record(&self) -> Option<&OwnedRecord> { + self.current.as_ref() + } + + pub fn is_empty(&self) -> bool { + self.current.is_none() + } + + pub fn get_null_flag(&self) -> bool { + self.null_flag + } + + pub fn set_null_flag(&mut self, flag: bool) { + self.null_flag = flag; + } + + pub fn next(&mut self) -> Result> { + // TODO + Ok(CursorResult::Ok(())) + } + pub fn prev(&mut self) -> Result> { + // TODO + Ok(CursorResult::Ok(())) + } + + pub fn exists(&mut self, key: &OwnedValue) -> Result> { + todo!() + } } diff --git a/core/schema.rs b/core/schema.rs index d445f1a77..68c8793b5 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -950,7 +950,17 @@ mod tests { #[derive(Debug)] pub struct EphemeralTable { - rows: BTreeMap>, // rowid -> each index in the Vec is a column - next_rowid: u64, // generate rowids - columns: Vec, // columns + pub rows: BTreeMap>, // rowid -> each index in the Vec is a column + pub next_rowid: u64, // generate rowids + pub columns: Vec, // columns +} + +impl EphemeralTable { + pub fn new() -> Self { + Self { + rows: todo!(), + next_rowid: todo!(), + columns: todo!(), + } + } } diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index a0bb63023..b33e6a24e 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1,4 +1,4 @@ -use crate::vdbe::builder::CursorType; +use crate::{ephemeral, vdbe::builder::CursorType}; use super::{Insn, InsnReference, OwnedValue, Program}; use std::rc::Rc; @@ -478,6 +478,9 @@ pub fn insn_to_str( let name = pseudo_table.columns.get(*column).unwrap().name.as_ref(); name } + CursorType::Ephemeral(ephemeral_table) => { + Some(&ephemeral_table.columns.get(*column).unwrap().name) + } CursorType::Sorter => None, CursorType::VirtualTable(v) => v.columns.get(*column).unwrap().name.as_ref(), }; @@ -1232,6 +1235,19 @@ pub fn insn_to_str( 0, "".to_string(), ), + Insn::OpenEphemeral { + cursor_id, + content_reg, + num_fields, + } => ( + "OpenEphemeral", + *cursor_id as i32, + *content_reg as i32, + *num_fields as i32, + OwnedValue::build_text(Rc::new("".to_string())), + 0, + format!("{} columns in r[{}]", num_fields, content_reg), + ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 223f321aa..92e161d27 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -639,6 +639,12 @@ pub enum Insn { db: usize, dest: usize, }, + /// Open a new cursor P1 to a transient table. + OpenEphemeral { + cursor_id: CursorID, + content_reg: usize, + num_fields: usize, + }, } fn cast_text_to_numerical(value: &str) -> OwnedValue { diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 0defd874a..8ae9afbf6 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -341,7 +341,6 @@ impl ProgramState { cursors, registers, result_row: None, - ephemeral_cursors, last_compare: None, deferred_seek: None, ended_coroutine: Bitfield::new(), @@ -397,7 +396,9 @@ macro_rules! must_be_btree_cursor { CursorType::BTreeTable(_) => get_cursor_as_table_mut(&mut $cursors, $cursor_id), CursorType::BTreeIndex(_) => get_cursor_as_index_mut(&mut $cursors, $cursor_id), CursorType::Pseudo(_) => panic!("{} on pseudo cursor", $insn_name), - CursorType::Ephemeral(_) => panic!("{} on ephemeral cursor", $insn_name), + CursorType::Ephemeral(_) => { + GeneralCursor::Ephemeral($ephemeral_cursors.get_mut(&$cursor_id).unwrap()) + } CursorType::Sorter => panic!("{} on sorter cursor", $insn_name), CursorType::VirtualTable(_) => panic!("{} on virtual table cursor", $insn_name), }; @@ -946,6 +947,15 @@ impl Program { .replace(Cursor::new_pseudo(cursor)); state.pc += 1; } + Insn::OpenEphemeral { + cursor_id, + content_reg: _, + num_fields: _, + } => { + let cursor = EphemeralCursor::new(); + ephemeral_cursors.insert(*cursor_id, cursor); + state.pc += 1; + } Insn::RewindAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); let cursor = @@ -1010,23 +1020,39 @@ impl Program { } let (_, cursor_type) = self.cursor_ref.get(*cursor_id).unwrap(); match cursor_type { - CursorType::BTreeTable(_) | CursorType::BTreeIndex(_) => { - let cursor = must_be_btree_cursor!( + CursorType::BTreeTable(_) + | CursorType::BTreeIndex(_) + | CursorType::Ephemeral(_) => { + match must_be_btree_cursor!( *cursor_id, self.cursor_ref, cursors, "Column" - ); - let record = cursor.record()?; - if let Some(record) = record.as_ref() { - state.registers[*dest] = if cursor.get_null_flag() { - OwnedValue::Null - } else { - record.values[*column].clone() - }; - } else { - state.registers[*dest] = OwnedValue::Null; - } + ) { + GeneralCursor::BTree(cursor) => { + let record = cursor.record()?; + if let Some(record) = record.as_ref() { + state.registers[*dest] = if cursor.get_null_flag() { + OwnedValue::Null + } else { + record.values[*column].clone() + }; + } else { + state.registers[*dest] = OwnedValue::Null; + } + } + GeneralCursor::Ephemeral(cursor) => { + if let Some(record) = cursor.record() { + state.registers[*dest] = if cursor.get_null_flag() { + OwnedValue::Null + } else { + record.values[*column].clone() + }; + } else { + state.registers[*dest] = OwnedValue::Null; + } + } + }; } CursorType::Sorter => { let cursor = get_cursor_as_sorter_mut(&mut cursors, *cursor_id); @@ -1049,7 +1075,14 @@ impl Program { "Insn::Column on virtual table cursor, use Insn::VColumn instead" ); } - CursorType::Ephemeral(ephemeral_table) => todo!(), + CursorType::Ephemeral(_) => { + let cursor = ephemeral_cursors.get_mut(cursor_id).unwrap(); + if let Some(record) = cursor.record() { + state.registers[*dest] = record.values[*column].clone(); + } else { + state.registers[*dest] = OwnedValue::Null + } + } } state.pc += 1; From 26e83ab4621da04f13ed214eeaf25ec2588719fd Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 22 Jan 2025 17:08:28 -0300 Subject: [PATCH 03/17] wip: implement main methods for ephemeral cursor --- core/ephemeral.rs | 485 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 479 insertions(+), 6 deletions(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index f6a47860b..cf09cd309 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -28,15 +28,88 @@ impl EphemeralCursor { } } + pub fn insert( + &mut self, + key: &OwnedValue, + record: &OwnedRecord, + moved_before: bool, + ) -> Result> { + let mut table = self.table.borrow_mut(); + + // Generate a new row ID if necessary + let rowid = if moved_before { + // Traverse to find the correct position (here, just use `key` as rowid for simplicity) + if let OwnedValue::Integer(rowid) = key { + *rowid as u64 + } else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + } + } else { + // Use the next available rowid + let rowid = table.next_rowid; + table.next_rowid += 1; + rowid + }; + + // Insert the record into the table + if table.rows.insert(rowid, record.values.clone()).is_some() { + // If a row already exists with the same rowid, overwrite it + self.rowid = Some(rowid); + self.current = Some(record.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + + // Update cursor state + self.rowid = Some(rowid); + self.current = Some(record.clone()); + self.null_flag = false; + + Ok(CursorResult::Ok(())) + } + pub fn rewind(&mut self) -> Result> { - todo!() + let table = self.table.borrow(); + let rows = &table.rows; + + if let Some((&first_rowid, row_data)) = rows.iter().next() { + self.rowid = Some(first_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + Ok(CursorResult::Ok(())) + } else { + self.rowid = None; + self.current = None; + self.null_flag = true; + Ok(CursorResult::Ok(())) + } } + pub fn last(&mut self) -> Result> { - todo!() + let table = self.table.borrow(); + let rows = &table.rows; + + if let Some((&last_rowid, row_data)) = rows.iter().next_back() { + self.rowid = Some(last_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + Ok(CursorResult::Ok(())) + } else { + self.rowid = None; + self.current = None; + self.null_flag = true; + Ok(CursorResult::Ok(())) + } } pub fn wait_for_completion(&mut self) -> Result<()> { - // TODO: Wait for pager I/O to complete + // Ephemeral operations should be sync Ok(()) } @@ -57,15 +130,415 @@ impl EphemeralCursor { } pub fn next(&mut self) -> Result> { - // TODO + let table = self.table.borrow(); + let rows = &table.rows; + + if self.rowid.is_none() { + if let Some((&first_rowid, row_data)) = rows.iter().next() { + self.rowid = Some(first_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } else if let Some(current_rowid) = self.rowid { + if let Some((&next_rowid, row_data)) = rows.range((current_rowid + 1)..).next() { + self.rowid = Some(next_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } + + // No more rows + self.null_flag = true; + self.rowid = None; + self.current = None; Ok(CursorResult::Ok(())) } pub fn prev(&mut self) -> Result> { - // TODO + let table = self.table.borrow(); + let rows = &table.rows; + + if self.rowid.is_none() { + if let Some((&first_rowid, row_data)) = rows.iter().next_back() { + self.rowid = Some(first_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } else if let Some(current_rowid) = self.rowid { + if let Some((&next_rowid, row_data)) = rows.range(..current_rowid).next_back() { + self.rowid = Some(next_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } + + // No more rows + self.null_flag = true; + self.rowid = None; + self.current = None; Ok(CursorResult::Ok(())) } pub fn exists(&mut self, key: &OwnedValue) -> Result> { - todo!() + let table = self.table.borrow(); + let rows = &table.rows; + + for (rowid, row_data) in rows.iter() { + if row_data.contains(key) { + self.rowid = Some(*rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(true)); + } + } + + // Key not found + self.rowid = None; + self.current = None; + self.null_flag = true; + Ok(CursorResult::Ok(false)) + } +} + +#[cfg(test)] +mod tests { + use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; + + use crate::{ + schema::EphemeralTable, + types::{CursorResult, LimboText, OwnedRecord, OwnedValue}, + }; + + use super::EphemeralCursor; + + #[test] + fn test_next() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + let val1 = vec![OwnedValue::Integer(42)]; + let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))]; + table.rows.insert(1, val1.clone()); + table.rows.insert(2, val2.clone()); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.next().unwrap(); // Move to the first row + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val1.clone() + }) + ); + + cursor.next().unwrap(); // Move to the second row + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val2.clone() + }) + ); + } + + #[test] + fn test_prev() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let val1 = vec![OwnedValue::Integer(42)]; + let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))]; + table.rows.insert(1, val1.clone()); + table.rows.insert(2, val2.clone()); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.prev().unwrap(); // Should move to row 2 + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val2.clone() + }) + ); + + cursor.prev().unwrap(); // Should move to row 1 + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val1.clone() + }) + ); + + cursor.prev().unwrap(); // Should go out of bounds + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + } + + #[test] + fn test_last() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let val1 = vec![OwnedValue::Integer(42)]; + let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))]; + table.rows.insert(1, val1.clone()); + table.rows.insert(2, val2.clone()); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.last().unwrap(); // Move to the last row + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val2.clone() + }) + ); + assert_eq!(cursor.rowid, Some(2)); + assert!(!cursor.null_flag); + } + + #[test] + fn test_last_empty_table() { + let table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.last().unwrap(); // Calling last on an empty table + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + assert!(cursor.rowid.is_none()); + } + + #[test] + fn test_rewind() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let val1 = vec![OwnedValue::Integer(42)]; + let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))]; + table.rows.insert(1, val1.clone()); + table.rows.insert(2, val2.clone()); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.rewind().unwrap(); // Move to the first row + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val1.clone() + }) + ); + assert_eq!(cursor.rowid, Some(1)); + assert!(!cursor.null_flag); + } + + #[test] + fn test_rewind_empty_table() { + let table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.rewind().unwrap(); // Calling rewind on an empty table + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + assert!(cursor.rowid.is_none()); + } + + #[test] + fn test_exists_key_found() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let val1 = OwnedValue::Integer(42); + let val2 = OwnedValue::Text(LimboText::new(Rc::new("Hello".to_string()))); + table.rows.insert(1, vec![val1.clone()]); + table.rows.insert(2, vec![val2.clone()]); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + let result = cursor.exists(&val1).unwrap(); + assert_eq!(result, CursorResult::Ok(true)); + assert_eq!(cursor.rowid, Some(1)); + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: vec![val1.clone()] + }) + ); + assert!(!cursor.null_flag); + } + + #[test] + fn test_exists_key_not_found() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let val1 = OwnedValue::Integer(42); + let val2 = OwnedValue::Text(LimboText::new(Rc::new("Hello".to_string()))); + table.rows.insert(1, vec![val1.clone()]); + table.rows.insert(2, vec![val2.clone()]); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + let result = cursor.exists(&OwnedValue::Integer(99)).unwrap(); + assert_eq!(result, CursorResult::Ok(false)); + assert!(cursor.rowid.is_none()); + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + } + + #[test] + fn test_insert_new_row() { + let table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + let key = OwnedValue::Integer(1); + let record = OwnedRecord { + values: vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))], + }; + + cursor.insert(&key, &record, false).unwrap(); + + let table = cursor.table.borrow(); + assert_eq!(table.rows.len(), 1); + assert_eq!(table.rows.get(&1), Some(&record.values)); + assert_eq!(cursor.rowid, Some(1)); + assert_eq!(cursor.current, Some(record)); + assert!(!cursor.null_flag); + } + + #[test] + fn test_insert_overwrite_row() { + let table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + let key = OwnedValue::Integer(1); + let record1 = OwnedRecord { + values: vec![OwnedValue::Text(LimboText::new(Rc::new( + "First".to_string(), + )))], + }; + let record2 = OwnedRecord { + values: vec![OwnedValue::Text(LimboText::new(Rc::new( + "Second".to_string(), + )))], + }; + + cursor.insert(&key, &record1, false).unwrap(); + cursor.insert(&key, &record2, true).unwrap(); + + let table = cursor.table.borrow(); + assert_eq!(table.rows.len(), 1); + assert_eq!(table.rows.get(&1), Some(&record2.values)); + assert_eq!(cursor.rowid, Some(1)); + assert_eq!(cursor.current, Some(record2)); + assert!(!cursor.null_flag); } } From 33336d3743e25f9324fe3890f1b1fc5e1d58bc5b Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 22 Jan 2025 17:17:50 -0300 Subject: [PATCH 04/17] Add do_seek() method to EphemeralCursor --- core/ephemeral.rs | 163 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 2 deletions(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index cf09cd309..503bf347c 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -5,7 +5,7 @@ use std::{ use crate::{ schema::EphemeralTable, - types::{CursorResult, OwnedRecord}, + types::{CursorResult, OwnedRecord, SeekKey, SeekOp}, LimboError, }; use crate::{types::OwnedValue, Result}; @@ -28,6 +28,66 @@ impl EphemeralCursor { } } + pub fn do_seek( + &mut self, + key: SeekKey<'_>, + op: SeekOp, + ) -> Result, Option)>> { + let table = self.table.borrow(); + let rows = &table.rows; + + match key { + SeekKey::TableRowId(rowid) => { + // Seek by row ID + let entry = match op { + SeekOp::EQ => rows.get(&rowid).map(|values| (rowid, values.clone())), + SeekOp::GE => rows + .range(rowid..) + .next() + .map(|(&id, values)| (id, values.clone())), + SeekOp::GT => rows + .range((rowid + 1)..) + .next() + .map(|(&id, values)| (id, values.clone())), + }; + + if let Some((id, values)) = entry { + self.rowid = Some(id); + self.current = Some(OwnedRecord { values }); + self.null_flag = false; + return Ok(CursorResult::Ok((Some(id), self.current.clone()))); + } + } + SeekKey::IndexKey(index_key) => { + // Seek by index key (ignoring row ID) + for (&rowid, values) in rows.iter() { + let record = OwnedRecord { + values: values.clone(), + }; + + let comparison = match op { + SeekOp::EQ => record == *index_key, + SeekOp::GE => record >= *index_key, + SeekOp::GT => record > *index_key, + }; + + if comparison { + self.rowid = Some(rowid); + self.current = Some(record); + self.null_flag = false; + return Ok(CursorResult::Ok((Some(rowid), self.current.clone()))); + } + } + } + } + + // No matching record found + self.rowid = None; + self.current = None; + self.null_flag = true; + Ok(CursorResult::Ok((None, None))) + } + pub fn insert( &mut self, key: &OwnedValue, @@ -219,7 +279,7 @@ mod tests { use crate::{ schema::EphemeralTable, - types::{CursorResult, LimboText, OwnedRecord, OwnedValue}, + types::{CursorResult, LimboText, OwnedRecord, OwnedValue, SeekKey, SeekOp}, }; use super::EphemeralCursor; @@ -541,4 +601,103 @@ mod tests { assert_eq!(cursor.current, Some(record2)); assert!(!cursor.null_flag); } + + #[test] + fn test_do_seek_by_rowid_eq() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + table.rows.insert(1, vec![OwnedValue::Integer(10)]); + table.rows.insert(2, vec![OwnedValue::Integer(20)]); + table.rows.insert(3, vec![OwnedValue::Integer(30)]); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + let result = cursor.do_seek(SeekKey::TableRowId(2), SeekOp::EQ).unwrap(); + assert_eq!( + result, + CursorResult::Ok(( + Some(2), + Some(OwnedRecord { + values: vec![OwnedValue::Integer(20)] + }) + )) + ); + assert_eq!(cursor.rowid, Some(2)); + assert_eq!(cursor.null_flag, false); + } + + #[test] + fn test_do_seek_by_index_key_ge() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + table.rows.insert(1, vec![OwnedValue::Integer(10)]); + table.rows.insert(2, vec![OwnedValue::Integer(20)]); + table.rows.insert(3, vec![OwnedValue::Integer(30)]); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + let key = OwnedRecord { + values: vec![OwnedValue::Integer(25)], + }; + + let result = cursor.do_seek(SeekKey::IndexKey(&key), SeekOp::GE).unwrap(); + assert_eq!( + result, + CursorResult::Ok(( + Some(3), + Some(OwnedRecord { + values: vec![OwnedValue::Integer(30)] + }) + )) + ); + assert_eq!(cursor.rowid, Some(3)); + assert_eq!(cursor.null_flag, false); + } + + #[test] + fn test_do_seek_no_match() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + table.rows.insert(1, vec![OwnedValue::Integer(10)]); + table.rows.insert(2, vec![OwnedValue::Integer(20)]); + table.rows.insert(3, vec![OwnedValue::Integer(30)]); + + let mut cursor = EphemeralCursor { + table: Rc::new(RefCell::new(table)), + rowid: None, + current: None, + null_flag: true, + }; + + let key = OwnedRecord { + values: vec![OwnedValue::Integer(40)], + }; + + let result = cursor.do_seek(SeekKey::IndexKey(&key), SeekOp::EQ).unwrap(); + assert_eq!(result, CursorResult::Ok((None, None))); + assert_eq!(cursor.rowid, None); + assert_eq!(cursor.null_flag, true); + } } From 1726f98e160ab6583b5cd22e4fce899f3abe4c03 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 22 Jan 2025 21:29:22 -0300 Subject: [PATCH 05/17] Fix new method in EphemeralTable --- core/schema.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/schema.rs b/core/schema.rs index 68c8793b5..7b3dc64dc 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -958,9 +958,9 @@ pub struct EphemeralTable { impl EphemeralTable { pub fn new() -> Self { Self { - rows: todo!(), - next_rowid: todo!(), - columns: todo!(), + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], } } } From 3a891a8fefdd712698678a392b43049f825328f5 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 22 Jan 2025 21:33:47 -0300 Subject: [PATCH 06/17] Apply clippy suggestions --- core/ephemeral.rs | 11 ++++------- core/schema.rs | 6 ++++++ core/vdbe/explain.rs | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index 503bf347c..0faaccb27 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -1,7 +1,4 @@ -use std::{ - cell::{Ref, RefCell}, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; use crate::{ schema::EphemeralTable, @@ -632,7 +629,7 @@ mod tests { )) ); assert_eq!(cursor.rowid, Some(2)); - assert_eq!(cursor.null_flag, false); + assert!(!cursor.null_flag); } #[test] @@ -669,7 +666,7 @@ mod tests { )) ); assert_eq!(cursor.rowid, Some(3)); - assert_eq!(cursor.null_flag, false); + assert!(!cursor.null_flag); } #[test] @@ -698,6 +695,6 @@ mod tests { let result = cursor.do_seek(SeekKey::IndexKey(&key), SeekOp::EQ).unwrap(); assert_eq!(result, CursorResult::Ok((None, None))); assert_eq!(cursor.rowid, None); - assert_eq!(cursor.null_flag, true); + assert!(cursor.null_flag); } } diff --git a/core/schema.rs b/core/schema.rs index 7b3dc64dc..fa6c3d66d 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -955,6 +955,12 @@ pub struct EphemeralTable { pub columns: Vec, // columns } +impl Default for EphemeralTable { + fn default() -> Self { + Self::new() + } +} + impl EphemeralTable { pub fn new() -> Self { Self { diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index b33e6a24e..5337eb957 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1,4 +1,4 @@ -use crate::{ephemeral, vdbe::builder::CursorType}; +use crate::vdbe::builder::CursorType; use super::{Insn, InsnReference, OwnedValue, Program}; use std::rc::Rc; From a52fd3a6b0d6402ba45594177fc7eb8cf56209e5 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Thu, 23 Jan 2025 11:28:39 -0300 Subject: [PATCH 07/17] Add OpenEphemeral to compat table --- COMPAT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index 1c2b9b227..dafeeed46 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -489,7 +489,7 @@ Modifiers: | Next | No | | | NextAsync | Yes | | | NextAwait | Yes | | -| Noop | Yes | | +| Noop | Yes | | | Not | Yes | | | NotExists | Yes | | | NotFound | No | | @@ -498,7 +498,7 @@ Modifiers: | NullRow | Yes | | | Once | No | | | OpenAutoindex | No | | -| OpenEphemeral | No | | +| OpenEphemeral | Yes | | | OpenPseudo | Yes | | | OpenRead | Yes | | | OpenReadAsync | Yes | | From 193d9fe8ab23040adc9cf042372ef61fb4aff6f6 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Sat, 25 Jan 2025 02:12:50 -0300 Subject: [PATCH 08/17] Implement And bytecode Take the logical AND of the values in registers P1 and P2 and write the result into register P3. If either P1 or P2 is 0 (false) then the result is 0 even if the other input is NULL. A NULL and true or two NULLs give a NULL output. --- core/vdbe/insn.rs | 7 +++++++ test | Bin 0 -> 4096 bytes 2 files changed, 7 insertions(+) create mode 100644 test diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 92e161d27..9580e5f2b 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -645,6 +645,13 @@ pub enum Insn { content_reg: usize, num_fields: usize, }, + + /// Take the logical AND of the values in registers P1 and P2 and write the result into register P3. + And { + lhs: usize, + rhs: usize, + dest: usize, + }, } fn cast_text_to_numerical(value: &str) -> OwnedValue { diff --git a/test b/test new file mode 100644 index 0000000000000000000000000000000000000000..70f8736482dac129c281c22dedc3655e589f1b21 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AO#03K;bV?9yWQnLNG(GE`kAw yc|jZjRH;$kXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjD`UHLI40Ks|e)) literal 0 HcmV?d00001 From ed2da7e4819657d14d4f3a9ba134bddd58d6c46a Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Sat, 25 Jan 2025 02:54:14 -0300 Subject: [PATCH 09/17] Add Or bytecode Take the logical OR of the values in register P1 and P2 and store the answer in register P3. If either P1 or P2 is nonzero (true) then the result is 1 (true) even if the other input is NULL. A NULL and false or two NULLs give a NULL output. --- core/vdbe/explain.rs | 9 +++++ core/vdbe/insn.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 5337eb957..87afb3ca1 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1248,6 +1248,15 @@ pub fn insn_to_str( 0, format!("{} columns in r[{}]", num_fields, content_reg), ), + Insn::Or { lhs, rhs, dest } => ( + "Or", + *rhs as i32, + *lhs as i32, + *dest as i32, + OwnedValue::build_text(Rc::new("".to_string())), + 0, + format!("r[{}]=(r[{}] || r[{}])", dest, lhs, rhs), + ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 9580e5f2b..59a2750b3 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -652,6 +652,12 @@ pub enum Insn { rhs: usize, dest: usize, }, + /// Take the logical OR of the values in register P1 and P2 and store the answer in register P3. + Or { + lhs: usize, + rhs: usize, + dest: usize, + }, } fn cast_text_to_numerical(value: &str) -> OwnedValue { @@ -1110,6 +1116,35 @@ pub fn exec_or(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { } } +pub fn exec_or(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { + if let OwnedValue::Agg(agg) = lhs { + lhs = agg.final_value(); + } + if let OwnedValue::Agg(agg) = rhs { + rhs = agg.final_value(); + } + + match (lhs, rhs) { + (OwnedValue::Null, OwnedValue::Null) + | (OwnedValue::Null, OwnedValue::Float(0.0)) + | (OwnedValue::Float(0.0), OwnedValue::Null) + | (OwnedValue::Null, OwnedValue::Integer(0)) + | (OwnedValue::Integer(0), OwnedValue::Null) => OwnedValue::Null, + (OwnedValue::Float(0.0), OwnedValue::Integer(0)) + | (OwnedValue::Integer(0), OwnedValue::Float(0.0)) + | (OwnedValue::Float(0.0), OwnedValue::Float(0.0)) + | (OwnedValue::Integer(0), OwnedValue::Integer(0)) => OwnedValue::Integer(0), + (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_or( + &cast_text_to_numerical(&lhs.value), + &cast_text_to_numerical(&rhs.value), + ), + (OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => { + exec_or(&cast_text_to_numerical(&text.value), other) + } + _ => OwnedValue::Integer(1), + } +} + #[cfg(test)] mod tests { use crate::{ @@ -1213,4 +1248,54 @@ mod tests { ); } } + + #[test] + fn test_exec_or() { + let inputs = vec![ + (OwnedValue::Integer(0), OwnedValue::Null), + (OwnedValue::Null, OwnedValue::Integer(1)), + (OwnedValue::Null, OwnedValue::Null), + (OwnedValue::Float(0.0), OwnedValue::Null), + (OwnedValue::Integer(1), OwnedValue::Float(2.2)), + (OwnedValue::Float(0.0), OwnedValue::Integer(0)), + ( + OwnedValue::Integer(0), + OwnedValue::Text(LimboText::new(Rc::new("string".to_string()))), + ), + ( + OwnedValue::Integer(0), + OwnedValue::Text(LimboText::new(Rc::new("1".to_string()))), + ), + ( + OwnedValue::Integer(0), + OwnedValue::Text(LimboText::new(Rc::new("".to_string()))), + ), + ]; + let outpus = vec![ + OwnedValue::Null, + OwnedValue::Integer(1), + OwnedValue::Null, + OwnedValue::Null, + OwnedValue::Integer(1), + OwnedValue::Integer(0), + OwnedValue::Integer(0), + OwnedValue::Integer(1), + OwnedValue::Integer(0), + ]; + + assert_eq!( + inputs.len(), + outpus.len(), + "Inputs and Outputs should have same size" + ); + for (i, (lhs, rhs)) in inputs.iter().enumerate() { + assert_eq!( + exec_or(lhs, rhs), + outpus[i], + "Wrong OR for lhs: {}, rhs: {}", + lhs, + rhs + ); + } + } } From 5feee10f1f39ee72c1034d6f894222b83dc4c4cb Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 29 Jan 2025 11:53:16 -0300 Subject: [PATCH 10/17] Adapt EphemeralCursor to recent changes and optimizations --- core/types.rs | 14 +++ core/vdbe/explain.rs | 9 -- core/vdbe/insn.rs | 92 ----------------- core/vdbe/mod.rs | 232 ++++++++++++++++++++++++++++++++----------- test | Bin 4096 -> 0 bytes 5 files changed, 186 insertions(+), 161 deletions(-) delete mode 100644 test diff --git a/core/types.rs b/core/types.rs index ae31314c1..8ebd97680 100644 --- a/core/types.rs +++ b/core/types.rs @@ -1,5 +1,6 @@ use limbo_ext::{AggCtx, FinalizeFunction, StepFunction}; +use crate::ephemeral::EphemeralCursor; use crate::error::LimboError; use crate::ext::{ExtValue, ExtValueType}; use crate::pseudo::PseudoCursor; @@ -670,6 +671,7 @@ pub enum Cursor { Table(BTreeCursor), Index(BTreeCursor), Pseudo(PseudoCursor), + Ephemeral(EphemeralCursor), Sorter(Sorter), Virtual(VTabOpaqueCursor), } @@ -691,6 +693,10 @@ impl Cursor { Self::Sorter(cursor) } + pub fn new_ephemeral(cursor: EphemeralCursor) -> Self { + Self::Ephemeral(cursor) + } + pub fn as_table_mut(&mut self) -> &mut BTreeCursor { match self { Self::Table(cursor) => cursor, @@ -725,8 +731,16 @@ impl Cursor { _ => panic!("Cursor is not a virtual cursor"), } } + + pub fn as_ephemeral_mut(&mut self) -> &mut EphemeralCursor { + match self { + Self::Ephemeral(cursor) => cursor, + _ => panic!("Cursor is not ephemeral"), + } + } } +#[derive(PartialEq, Debug)] pub enum CursorResult { Ok(T), IO, diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 87afb3ca1..5337eb957 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1248,15 +1248,6 @@ pub fn insn_to_str( 0, format!("{} columns in r[{}]", num_fields, content_reg), ), - Insn::Or { lhs, rhs, dest } => ( - "Or", - *rhs as i32, - *lhs as i32, - *dest as i32, - OwnedValue::build_text(Rc::new("".to_string())), - 0, - format!("r[{}]=(r[{}] || r[{}])", dest, lhs, rhs), - ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 59a2750b3..92e161d27 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -645,19 +645,6 @@ pub enum Insn { content_reg: usize, num_fields: usize, }, - - /// Take the logical AND of the values in registers P1 and P2 and write the result into register P3. - And { - lhs: usize, - rhs: usize, - dest: usize, - }, - /// Take the logical OR of the values in register P1 and P2 and store the answer in register P3. - Or { - lhs: usize, - rhs: usize, - dest: usize, - }, } fn cast_text_to_numerical(value: &str) -> OwnedValue { @@ -1116,35 +1103,6 @@ pub fn exec_or(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { } } -pub fn exec_or(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { - if let OwnedValue::Agg(agg) = lhs { - lhs = agg.final_value(); - } - if let OwnedValue::Agg(agg) = rhs { - rhs = agg.final_value(); - } - - match (lhs, rhs) { - (OwnedValue::Null, OwnedValue::Null) - | (OwnedValue::Null, OwnedValue::Float(0.0)) - | (OwnedValue::Float(0.0), OwnedValue::Null) - | (OwnedValue::Null, OwnedValue::Integer(0)) - | (OwnedValue::Integer(0), OwnedValue::Null) => OwnedValue::Null, - (OwnedValue::Float(0.0), OwnedValue::Integer(0)) - | (OwnedValue::Integer(0), OwnedValue::Float(0.0)) - | (OwnedValue::Float(0.0), OwnedValue::Float(0.0)) - | (OwnedValue::Integer(0), OwnedValue::Integer(0)) => OwnedValue::Integer(0), - (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_or( - &cast_text_to_numerical(&lhs.value), - &cast_text_to_numerical(&rhs.value), - ), - (OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => { - exec_or(&cast_text_to_numerical(&text.value), other) - } - _ => OwnedValue::Integer(1), - } -} - #[cfg(test)] mod tests { use crate::{ @@ -1248,54 +1206,4 @@ mod tests { ); } } - - #[test] - fn test_exec_or() { - let inputs = vec![ - (OwnedValue::Integer(0), OwnedValue::Null), - (OwnedValue::Null, OwnedValue::Integer(1)), - (OwnedValue::Null, OwnedValue::Null), - (OwnedValue::Float(0.0), OwnedValue::Null), - (OwnedValue::Integer(1), OwnedValue::Float(2.2)), - (OwnedValue::Float(0.0), OwnedValue::Integer(0)), - ( - OwnedValue::Integer(0), - OwnedValue::Text(LimboText::new(Rc::new("string".to_string()))), - ), - ( - OwnedValue::Integer(0), - OwnedValue::Text(LimboText::new(Rc::new("1".to_string()))), - ), - ( - OwnedValue::Integer(0), - OwnedValue::Text(LimboText::new(Rc::new("".to_string()))), - ), - ]; - let outpus = vec![ - OwnedValue::Null, - OwnedValue::Integer(1), - OwnedValue::Null, - OwnedValue::Null, - OwnedValue::Integer(1), - OwnedValue::Integer(0), - OwnedValue::Integer(0), - OwnedValue::Integer(1), - OwnedValue::Integer(0), - ]; - - assert_eq!( - inputs.len(), - outpus.len(), - "Inputs and Outputs should have same size" - ); - for (i, (lhs, rhs)) in inputs.iter().enumerate() { - assert_eq!( - exec_or(lhs, rhs), - outpus[i], - "Wrong OR for lhs: {}, rhs: {}", - lhs, - rhs - ); - } - } } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 8ae9afbf6..e9098cb54 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -282,6 +282,17 @@ fn get_cursor_as_virtual_mut<'long, 'short>( cursor } +fn get_cursor_as_ephemeral_mut<'long, 'short>( + cursors: &'short mut RefMut<'long, Vec>>, + cursor_id: CursorID, +) -> &'short mut EphemeralCursor { + cursors + .get_mut(cursor_id) + .expect("cursor id out of bounds") + .as_mut() + .expect("cursor not allocated") + .as_ephemeral_mut() +} struct Bitfield([u64; N]); impl Bitfield { @@ -305,6 +316,7 @@ impl Bitfield { } } +<<<<<<< HEAD pub struct VTabOpaqueCursor(*mut c_void); impl VTabOpaqueCursor { @@ -315,6 +327,11 @@ impl VTabOpaqueCursor { pub fn as_ptr(&self) -> *mut c_void { self.0 } +======= +pub enum GeneralCursor<'a> { + BTree(&'a mut BTreeCursor), + Ephemeral(&'a mut EphemeralCursor), +>>>>>>> fa7db68d (Adapt EphemeralCursor to recent changes and optimizations) } /// The program state describes the environment in which the program executes. @@ -393,12 +410,16 @@ macro_rules! must_be_btree_cursor { ($cursor_id:expr, $cursor_ref:expr, $cursors:expr, $insn_name:expr) => {{ let (_, cursor_type) = $cursor_ref.get($cursor_id).unwrap(); let cursor = match cursor_type { - CursorType::BTreeTable(_) => get_cursor_as_table_mut(&mut $cursors, $cursor_id), - CursorType::BTreeIndex(_) => get_cursor_as_index_mut(&mut $cursors, $cursor_id), - CursorType::Pseudo(_) => panic!("{} on pseudo cursor", $insn_name), + CursorType::BTreeTable(_) => { + GeneralCursor::BTree(get_cursor_as_table_mut(&mut $cursors, $cursor_id)) + } + CursorType::BTreeIndex(_) => { + GeneralCursor::BTree(get_cursor_as_index_mut(&mut $cursors, $cursor_id)) + } CursorType::Ephemeral(_) => { - GeneralCursor::Ephemeral($ephemeral_cursors.get_mut(&$cursor_id).unwrap()) + GeneralCursor::Ephemeral(get_cursor_as_ephemeral_mut(&mut $cursors, $cursor_id)) } + CursorType::Pseudo(_) => panic!("{} on pseudo cursor", $insn_name), CursorType::Sorter => panic!("{} on sorter cursor", $insn_name), CursorType::VirtualTable(_) => panic!("{} on virtual table cursor", $insn_name), }; @@ -529,9 +550,10 @@ impl Program { } Insn::NullRow { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NullRow"); - cursor.set_null_flag(true); + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NullRow") { + GeneralCursor::BTree(cursor) => cursor.set_null_flag(true), + GeneralCursor::Ephemeral(cursor) => cursor.set_null_flag(true), + } state.pc += 1; } Insn::Compare { @@ -836,24 +858,27 @@ impl Program { root_page, } => { let (_, cursor_type) = self.cursor_ref.get(*cursor_id).unwrap(); - let cursor = BTreeCursor::new(pager.clone(), *root_page); let mut cursors = state.cursors.borrow_mut(); match cursor_type { CursorType::BTreeTable(_) => { + let cursor = BTreeCursor::new(pager.clone(), *root_page); cursors .get_mut(*cursor_id) .unwrap() .replace(Cursor::new_table(cursor)); } CursorType::BTreeIndex(_) => { + let cursor = BTreeCursor::new(pager.clone(), *root_page); cursors .get_mut(*cursor_id) .unwrap() .replace(Cursor::new_index(cursor)); } CursorType::Ephemeral(_) => { - let cursor = EphemeralCursor::new(); - ephemeral_cursors.insert(*cursor_id, cursor); + cursors + .get_mut(*cursor_id) + .unwrap() + .replace(Cursor::new_ephemeral(EphemeralCursor::new())); } CursorType::Pseudo(_) => { panic!("OpenReadAsync on pseudo cursor"); @@ -952,22 +977,41 @@ impl Program { content_reg: _, num_fields: _, } => { + let mut cursors = state.cursors.borrow_mut(); let cursor = EphemeralCursor::new(); - ephemeral_cursors.insert(*cursor_id, cursor); + cursors + .get_mut(*cursor_id) + .unwrap() + .replace(Cursor::new_ephemeral(cursor)); state.pc += 1; } Insn::RewindAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "RewindAsync"); - return_if_io!(cursor.rewind()); + + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "RewindAsync") + { + GeneralCursor::BTree(cursor) => return_if_io!(cursor.rewind()), + GeneralCursor::Ephemeral(cursor) => return_if_io!(cursor.rewind()), + }; + + state.pc += 1; + } + Insn::LastAsync { cursor_id } => { + let mut cursors = state.cursors.borrow_mut(); + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "RewindAsync") + { + GeneralCursor::BTree(cursor) => return_if_io!(cursor.rewind()), + GeneralCursor::Ephemeral(cursor) => return_if_io!(cursor.rewind()), + } + state.pc += 1; } Insn::LastAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAsync"); - return_if_io!(cursor.last()); + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAsync") { + GeneralCursor::BTree(cursor) => return_if_io!(cursor.last()), + GeneralCursor::Ephemeral(cursor) => return_if_io!(cursor.last()), + } state.pc += 1; } Insn::LastAwait { @@ -976,13 +1020,24 @@ impl Program { } => { assert!(pc_if_empty.is_offset()); let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAwait"); - cursor.wait_for_completion()?; - if cursor.is_empty() { - state.pc = pc_if_empty.to_offset_int(); - } else { - state.pc += 1; + + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAwait") { + GeneralCursor::BTree(cursor) => { + cursor.wait_for_completion()?; + if cursor.is_empty() { + state.pc = pc_if_empty.to_offset_int(); + } else { + state.pc += 1; + } + } + GeneralCursor::Ephemeral(cursor) => { + cursor.wait_for_completion()?; + if cursor.is_empty() { + state.pc = pc_if_empty.to_offset_int(); + } else { + state.pc += 1; + } + } } } Insn::RewindAwait { @@ -991,13 +1046,25 @@ impl Program { } => { assert!(pc_if_empty.is_offset()); let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "RewindAwait"); - cursor.wait_for_completion()?; - if cursor.is_empty() { - state.pc = pc_if_empty.to_offset_int(); - } else { - state.pc += 1; + + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "RewindAwait") + { + GeneralCursor::BTree(cursor) => { + cursor.wait_for_completion()?; + if cursor.is_empty() { + state.pc = pc_if_empty.to_offset_int(); + } else { + state.pc += 1; + } + } + GeneralCursor::Ephemeral(cursor) => { + cursor.wait_for_completion()?; + if cursor.is_empty() { + state.pc = pc_if_empty.to_offset_int(); + } else { + state.pc += 1; + } + } } } Insn::Column { @@ -1104,18 +1171,32 @@ impl Program { } Insn::NextAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NextAsync"); - cursor.set_null_flag(false); - return_if_io!(cursor.next()); + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NextAsync") { + GeneralCursor::BTree(cursor) => { + cursor.set_null_flag(false); + return_if_io!(cursor.next()); + } + GeneralCursor::Ephemeral(cursor) => { + cursor.set_null_flag(false); + return_if_io!(cursor.next()); + } + } + state.pc += 1; } Insn::PrevAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "PrevAsync"); - cursor.set_null_flag(false); - return_if_io!(cursor.prev()); + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "PrevAsync") { + GeneralCursor::BTree(cursor) => { + cursor.set_null_flag(false); + return_if_io!(cursor.prev()); + } + GeneralCursor::Ephemeral(cursor) => { + cursor.set_null_flag(false); + return_if_io!(cursor.prev()); + } + } + state.pc += 1; } Insn::PrevAwait { @@ -1124,13 +1205,24 @@ impl Program { } => { let mut cursors = state.cursors.borrow_mut(); assert!(pc_if_next.is_offset()); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "PrevAwait"); - cursor.wait_for_completion()?; - if !cursor.is_empty() { - state.pc = pc_if_next.to_offset_int(); - } else { - state.pc += 1; + + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "PrevAwait") { + GeneralCursor::BTree(cursor) => { + cursor.wait_for_completion()?; + if !cursor.is_empty() { + state.pc = pc_if_next.to_offset_int(); + } else { + state.pc += 1; + } + } + GeneralCursor::Ephemeral(cursor) => { + cursor.wait_for_completion()?; + if !cursor.is_empty() { + state.pc = pc_if_next.to_offset_int(); + } else { + state.pc += 1; + } + } } } Insn::NextAwait { @@ -1139,13 +1231,23 @@ impl Program { } => { assert!(pc_if_next.is_offset()); let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NextAwait"); - cursor.wait_for_completion()?; - if !cursor.is_empty() { - state.pc = pc_if_next.to_offset_int(); - } else { - state.pc += 1; + match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NextAwait") { + GeneralCursor::BTree(cursor) => { + cursor.wait_for_completion()?; + if !cursor.is_empty() { + state.pc = pc_if_next.to_offset_int(); + } else { + state.pc += 1; + } + } + GeneralCursor::Ephemeral(cursor) => { + cursor.wait_for_completion()?; + if !cursor.is_empty() { + state.pc = pc_if_next.to_offset_int(); + } else { + state.pc += 1; + } + } } } Insn::Halt { @@ -2574,13 +2676,23 @@ impl Program { target_pc, } => { let mut cursors = state.cursors.borrow_mut(); - let cursor = - must_be_btree_cursor!(*cursor, self.cursor_ref, cursors, "NotExists"); - let exists = return_if_io!(cursor.exists(&state.registers[*rowid_reg])); - if exists { - state.pc += 1; - } else { - state.pc = target_pc.to_offset_int(); + match must_be_btree_cursor!(*cursor, self.cursor_ref, cursors, "NotExists") { + GeneralCursor::BTree(cursor) => { + let exists = return_if_io!(cursor.exists(&state.registers[*rowid_reg])); + if exists { + state.pc += 1; + } else { + state.pc = target_pc.to_offset_int(); + } + } + GeneralCursor::Ephemeral(cursor) => { + let exists = return_if_io!(cursor.exists(&state.registers[*rowid_reg])); + if exists { + state.pc += 1; + } else { + state.pc = target_pc.to_offset_int(); + } + } } } Insn::OffsetLimit { diff --git a/test b/test deleted file mode 100644 index 70f8736482dac129c281c22dedc3655e589f1b21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AO#03K;bV?9yWQnLNG(GE`kAw yc|jZjRH;$kXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjD`UHLI40Ks|e)) From c2386261d22f97d653c8bce1f40632eedc85c11b Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 29 Jan 2025 12:09:40 -0300 Subject: [PATCH 11/17] Fix clippy warnings --- core/ephemeral.rs | 1 + core/schema.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index 0faaccb27..93d48e6cc 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -14,6 +14,7 @@ pub struct EphemeralCursor { null_flag: bool, } +#[allow(dead_code)] impl EphemeralCursor { pub fn new() -> Self { let table = Rc::new(RefCell::new(EphemeralTable::new())); diff --git a/core/schema.rs b/core/schema.rs index fa6c3d66d..4771f755b 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -50,6 +50,7 @@ pub enum Table { BTree(Rc), Pseudo(Rc), Virtual(Rc), + #[allow(dead_code)] EphemeralTable(Rc), } From 3deef2eae16d7d48368a44049bba076817eff5f8 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Fri, 31 Jan 2025 18:35:03 -0300 Subject: [PATCH 12/17] Update macro name must_be_btree_cursor to must_be_general_cursor --- core/vdbe/mod.rs | 50 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index e9098cb54..701a3a2b1 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -406,7 +406,7 @@ impl ProgramState { } } -macro_rules! must_be_btree_cursor { +macro_rules! must_be_general_cursor { ($cursor_id:expr, $cursor_ref:expr, $cursors:expr, $insn_name:expr) => {{ let (_, cursor_type) = $cursor_ref.get($cursor_id).unwrap(); let cursor = match cursor_type { @@ -550,7 +550,7 @@ impl Program { } Insn::NullRow { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NullRow") { + match must_be_general_cursor!(*cursor_id, self.cursor_ref, cursors, "NullRow") { GeneralCursor::BTree(cursor) => cursor.set_null_flag(true), GeneralCursor::Ephemeral(cursor) => cursor.set_null_flag(true), } @@ -988,8 +988,12 @@ impl Program { Insn::RewindAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "RewindAsync") - { + match must_be_general_cursor!( + *cursor_id, + self.cursor_ref, + cursors, + "RewindAsync" + ) { GeneralCursor::BTree(cursor) => return_if_io!(cursor.rewind()), GeneralCursor::Ephemeral(cursor) => return_if_io!(cursor.rewind()), }; @@ -998,8 +1002,12 @@ impl Program { } Insn::LastAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "RewindAsync") - { + match must_be_general_cursor!( + *cursor_id, + self.cursor_ref, + cursors, + "RewindAsync" + ) { GeneralCursor::BTree(cursor) => return_if_io!(cursor.rewind()), GeneralCursor::Ephemeral(cursor) => return_if_io!(cursor.rewind()), } @@ -1008,7 +1016,8 @@ impl Program { } Insn::LastAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAsync") { + match must_be_general_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAsync") + { GeneralCursor::BTree(cursor) => return_if_io!(cursor.last()), GeneralCursor::Ephemeral(cursor) => return_if_io!(cursor.last()), } @@ -1021,7 +1030,8 @@ impl Program { assert!(pc_if_empty.is_offset()); let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAwait") { + match must_be_general_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAwait") + { GeneralCursor::BTree(cursor) => { cursor.wait_for_completion()?; if cursor.is_empty() { @@ -1047,8 +1057,12 @@ impl Program { assert!(pc_if_empty.is_offset()); let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "RewindAwait") - { + match must_be_general_cursor!( + *cursor_id, + self.cursor_ref, + cursors, + "RewindAwait" + ) { GeneralCursor::BTree(cursor) => { cursor.wait_for_completion()?; if cursor.is_empty() { @@ -1090,7 +1104,7 @@ impl Program { CursorType::BTreeTable(_) | CursorType::BTreeIndex(_) | CursorType::Ephemeral(_) => { - match must_be_btree_cursor!( + match must_be_general_cursor!( *cursor_id, self.cursor_ref, cursors, @@ -1171,7 +1185,8 @@ impl Program { } Insn::NextAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NextAsync") { + match must_be_general_cursor!(*cursor_id, self.cursor_ref, cursors, "NextAsync") + { GeneralCursor::BTree(cursor) => { cursor.set_null_flag(false); return_if_io!(cursor.next()); @@ -1186,7 +1201,8 @@ impl Program { } Insn::PrevAsync { cursor_id } => { let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "PrevAsync") { + match must_be_general_cursor!(*cursor_id, self.cursor_ref, cursors, "PrevAsync") + { GeneralCursor::BTree(cursor) => { cursor.set_null_flag(false); return_if_io!(cursor.prev()); @@ -1206,7 +1222,8 @@ impl Program { let mut cursors = state.cursors.borrow_mut(); assert!(pc_if_next.is_offset()); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "PrevAwait") { + match must_be_general_cursor!(*cursor_id, self.cursor_ref, cursors, "PrevAwait") + { GeneralCursor::BTree(cursor) => { cursor.wait_for_completion()?; if !cursor.is_empty() { @@ -1231,7 +1248,8 @@ impl Program { } => { assert!(pc_if_next.is_offset()); let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor_id, self.cursor_ref, cursors, "NextAwait") { + match must_be_general_cursor!(*cursor_id, self.cursor_ref, cursors, "NextAwait") + { GeneralCursor::BTree(cursor) => { cursor.wait_for_completion()?; if !cursor.is_empty() { @@ -2676,7 +2694,7 @@ impl Program { target_pc, } => { let mut cursors = state.cursors.borrow_mut(); - match must_be_btree_cursor!(*cursor, self.cursor_ref, cursors, "NotExists") { + match must_be_general_cursor!(*cursor, self.cursor_ref, cursors, "NotExists") { GeneralCursor::BTree(cursor) => { let exists = return_if_io!(cursor.exists(&state.registers[*rowid_reg])); if exists { From bd433ab5b0eb70d43091b695796c27be5aa7137a Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Sat, 1 Feb 2025 23:58:30 -0300 Subject: [PATCH 13/17] Removes Refcell from EphemeralTable --- core/ephemeral.rs | 74 +++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index 93d48e6cc..a02ac478a 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -1,5 +1,3 @@ -use std::{cell::RefCell, rc::Rc}; - use crate::{ schema::EphemeralTable, types::{CursorResult, OwnedRecord, SeekKey, SeekOp}, @@ -8,7 +6,7 @@ use crate::{ use crate::{types::OwnedValue, Result}; pub struct EphemeralCursor { - table: Rc>, + table: EphemeralTable, rowid: Option, current: Option, null_flag: bool, @@ -17,9 +15,8 @@ pub struct EphemeralCursor { #[allow(dead_code)] impl EphemeralCursor { pub fn new() -> Self { - let table = Rc::new(RefCell::new(EphemeralTable::new())); Self { - table, + table: EphemeralTable::new(), rowid: None, current: None, null_flag: false, @@ -31,8 +28,7 @@ impl EphemeralCursor { key: SeekKey<'_>, op: SeekOp, ) -> Result, Option)>> { - let table = self.table.borrow(); - let rows = &table.rows; + let rows = &self.table.rows; match key { SeekKey::TableRowId(rowid) => { @@ -92,8 +88,6 @@ impl EphemeralCursor { record: &OwnedRecord, moved_before: bool, ) -> Result> { - let mut table = self.table.borrow_mut(); - // Generate a new row ID if necessary let rowid = if moved_before { // Traverse to find the correct position (here, just use `key` as rowid for simplicity) @@ -106,13 +100,18 @@ impl EphemeralCursor { } } else { // Use the next available rowid - let rowid = table.next_rowid; - table.next_rowid += 1; + let rowid = self.table.next_rowid; + self.table.next_rowid += 1; rowid }; // Insert the record into the table - if table.rows.insert(rowid, record.values.clone()).is_some() { + if self + .table + .rows + .insert(rowid, record.values.clone()) + .is_some() + { // If a row already exists with the same rowid, overwrite it self.rowid = Some(rowid); self.current = Some(record.clone()); @@ -129,8 +128,7 @@ impl EphemeralCursor { } pub fn rewind(&mut self) -> Result> { - let table = self.table.borrow(); - let rows = &table.rows; + let rows = &self.table.rows; if let Some((&first_rowid, row_data)) = rows.iter().next() { self.rowid = Some(first_rowid); @@ -148,8 +146,7 @@ impl EphemeralCursor { } pub fn last(&mut self) -> Result> { - let table = self.table.borrow(); - let rows = &table.rows; + let rows = &self.table.rows; if let Some((&last_rowid, row_data)) = rows.iter().next_back() { self.rowid = Some(last_rowid); @@ -188,8 +185,7 @@ impl EphemeralCursor { } pub fn next(&mut self) -> Result> { - let table = self.table.borrow(); - let rows = &table.rows; + let rows = &self.table.rows; if self.rowid.is_none() { if let Some((&first_rowid, row_data)) = rows.iter().next() { @@ -218,8 +214,7 @@ impl EphemeralCursor { Ok(CursorResult::Ok(())) } pub fn prev(&mut self) -> Result> { - let table = self.table.borrow(); - let rows = &table.rows; + let rows = &self.table.rows; if self.rowid.is_none() { if let Some((&first_rowid, row_data)) = rows.iter().next_back() { @@ -249,8 +244,7 @@ impl EphemeralCursor { } pub fn exists(&mut self, key: &OwnedValue) -> Result> { - let table = self.table.borrow(); - let rows = &table.rows; + let rows = &self.table.rows; for (rowid, row_data) in rows.iter() { if row_data.contains(key) { @@ -297,7 +291,7 @@ mod tests { table.rows.insert(2, val2.clone()); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -336,7 +330,7 @@ mod tests { table.rows.insert(2, val2.clone()); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -379,7 +373,7 @@ mod tests { table.rows.insert(2, val2.clone()); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -405,7 +399,7 @@ mod tests { }; let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -433,7 +427,7 @@ mod tests { table.rows.insert(2, val2.clone()); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -459,7 +453,7 @@ mod tests { }; let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -485,7 +479,7 @@ mod tests { table.rows.insert(2, vec![val2.clone()]); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -517,7 +511,7 @@ mod tests { table.rows.insert(2, vec![val2.clone()]); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -539,7 +533,7 @@ mod tests { }; let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -554,9 +548,8 @@ mod tests { cursor.insert(&key, &record, false).unwrap(); - let table = cursor.table.borrow(); - assert_eq!(table.rows.len(), 1); - assert_eq!(table.rows.get(&1), Some(&record.values)); + assert_eq!(cursor.table.rows.len(), 1); + assert_eq!(cursor.table.rows.get(&1), Some(&record.values)); assert_eq!(cursor.rowid, Some(1)); assert_eq!(cursor.current, Some(record)); assert!(!cursor.null_flag); @@ -571,7 +564,7 @@ mod tests { }; let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -592,9 +585,8 @@ mod tests { cursor.insert(&key, &record1, false).unwrap(); cursor.insert(&key, &record2, true).unwrap(); - let table = cursor.table.borrow(); - assert_eq!(table.rows.len(), 1); - assert_eq!(table.rows.get(&1), Some(&record2.values)); + assert_eq!(cursor.table.rows.len(), 1); + assert_eq!(cursor.table.rows.get(&1), Some(&record2.values)); assert_eq!(cursor.rowid, Some(1)); assert_eq!(cursor.current, Some(record2)); assert!(!cursor.null_flag); @@ -613,7 +605,7 @@ mod tests { table.rows.insert(3, vec![OwnedValue::Integer(30)]); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -646,7 +638,7 @@ mod tests { table.rows.insert(3, vec![OwnedValue::Integer(30)]); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, @@ -683,7 +675,7 @@ mod tests { table.rows.insert(3, vec![OwnedValue::Integer(30)]); let mut cursor = EphemeralCursor { - table: Rc::new(RefCell::new(table)), + table, rowid: None, current: None, null_flag: true, From bb03ad89180e5d2738f3056a644ab8268e388423 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Tue, 4 Feb 2025 17:02:29 -0300 Subject: [PATCH 14/17] wip: adding support to EphemeralIndexes in EphemeralCursor --- core/ephemeral.rs | 1516 +++++++++++++++++++++++++++--------------- core/schema.rs | 20 + core/types.rs | 24 +- core/vdbe/builder.rs | 5 +- core/vdbe/explain.rs | 18 +- core/vdbe/insn.rs | 3 +- core/vdbe/mod.rs | 75 ++- 7 files changed, 1095 insertions(+), 566 deletions(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index a02ac478a..135931981 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -1,24 +1,37 @@ use crate::{ - schema::EphemeralTable, + schema::{EphemeralIndex, EphemeralTable}, types::{CursorResult, OwnedRecord, SeekKey, SeekOp}, LimboError, }; use crate::{types::OwnedValue, Result}; - pub struct EphemeralCursor { - table: EphemeralTable, + source: Ephemeral, rowid: Option, current: Option, null_flag: bool, } +enum Ephemeral { + Table(EphemeralTable), + Index(EphemeralIndex), +} + #[allow(dead_code)] impl EphemeralCursor { - pub fn new() -> Self { + pub fn new_with_table() -> Self { Self { - table: EphemeralTable::new(), + source: Ephemeral::Table(EphemeralTable::new()), + current: None, rowid: None, + null_flag: false, + } + } + + pub fn new_with_index() -> Self { + Self { + source: Ephemeral::Index(EphemeralIndex::new()), current: None, + rowid: None, null_flag: false, } } @@ -28,10 +41,13 @@ impl EphemeralCursor { key: SeekKey<'_>, op: SeekOp, ) -> Result, Option)>> { - let rows = &self.table.rows; + match &mut self.source { + Ephemeral::Table(table) => { + let SeekKey::TableRowId(rowid) = key else { + unreachable!("table seek key should be a rowid"); + }; + let rows = &table.rows; - match key { - SeekKey::TableRowId(rowid) => { // Seek by row ID let entry = match op { SeekOp::EQ => rows.get(&rowid).map(|values| (rowid, values.clone())), @@ -52,25 +68,25 @@ impl EphemeralCursor { return Ok(CursorResult::Ok((Some(id), self.current.clone()))); } } - SeekKey::IndexKey(index_key) => { - // Seek by index key (ignoring row ID) - for (&rowid, values) in rows.iter() { - let record = OwnedRecord { - values: values.clone(), - }; - - let comparison = match op { - SeekOp::EQ => record == *index_key, - SeekOp::GE => record >= *index_key, - SeekOp::GT => record > *index_key, - }; - - if comparison { - self.rowid = Some(rowid); - self.current = Some(record); - self.null_flag = false; - return Ok(CursorResult::Ok((Some(rowid), self.current.clone()))); - } + Ephemeral::Index(index) => { + let SeekKey::IndexKey(index_key) = key else { + unreachable!("index seek key should be a record"); + }; + + let rows = &index.rows; + let index = index_key.values.first().expect("No values in index record"); + + let mut range = match op { + SeekOp::EQ => rows.range((index.clone(), 0)..=(index.clone(), u64::MAX)), + SeekOp::GE => rows.range((index.clone(), 0)..), + SeekOp::GT => rows.range((index.clone(), 0)..), // To exclude index we need to implement Ord for OwnedValue + }; + + if let Some((index, row)) = range.next() { + self.rowid = Some(index.1); + self.current = Some(row.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok((self.rowid, self.current.clone()))); } } } @@ -88,79 +104,134 @@ impl EphemeralCursor { record: &OwnedRecord, moved_before: bool, ) -> Result> { - // Generate a new row ID if necessary - let rowid = if moved_before { - // Traverse to find the correct position (here, just use `key` as rowid for simplicity) - if let OwnedValue::Integer(rowid) = key { - *rowid as u64 - } else { - return Err(LimboError::InternalError( - "Invalid key type for rowid".to_string(), - )); + match &mut self.source { + Ephemeral::Table(table) => { + let rowid = if moved_before { + // Traverse to find the correct position (here, just use `key` as rowid for simplicity) + if let OwnedValue::Integer(rowid) = key { + *rowid as u64 + } else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + } + } else { + // Use the next available rowid + let rowid = table.next_rowid; + table.next_rowid += 1; + rowid + }; + + // Insert the record into the table + table.rows.insert(rowid, record.values.clone()); + + // Update cursor state + self.rowid = Some(rowid); + self.current = Some(record.clone()); + self.null_flag = false; + + return Ok(CursorResult::Ok(())); } - } else { - // Use the next available rowid - let rowid = self.table.next_rowid; - self.table.next_rowid += 1; - rowid - }; + Ephemeral::Index(index) => { + let OwnedValue::Integer(rowid) = key else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + }; - // Insert the record into the table - if self - .table - .rows - .insert(rowid, record.values.clone()) - .is_some() - { - // If a row already exists with the same rowid, overwrite it - self.rowid = Some(rowid); - self.current = Some(record.clone()); - self.null_flag = false; - return Ok(CursorResult::Ok(())); - } + let key = match record.values.first().expect("No values in index record") { + OwnedValue::Null | OwnedValue::Agg(_) | OwnedValue::Record(_) => { + return Err(LimboError::InternalError( + "Key of index cannot be Null, Agg nor Record".to_string(), + )); + } + OwnedValue::Integer(value) => OwnedValue::Integer(*value), + OwnedValue::Float(value) => OwnedValue::Float(*value), + OwnedValue::Text(value) => OwnedValue::Text(value.clone()), + OwnedValue::Blob(value) => OwnedValue::Blob(value.clone()), + }; - // Update cursor state - self.rowid = Some(rowid); - self.current = Some(record.clone()); - self.null_flag = false; + // Check existing index entries for type consistency + // TODO: probably this should be inside the EphemeralIndex + if let Some((existing_key, _)) = index.rows.iter().next() { + if !matches!( + (existing_key, &key), + ((OwnedValue::Integer(_), _), OwnedValue::Integer(_)) + | ((OwnedValue::Float(_), _), OwnedValue::Float(_)) + | ((OwnedValue::Text(_), _), OwnedValue::Text(_)) + | ((OwnedValue::Blob(_), _), OwnedValue::Blob(_)) + ) { + return Err(LimboError::InternalError( + "Mismatched key type in index".to_string(), + )); + } + } - Ok(CursorResult::Ok(())) - } + index.rows.insert((key, *rowid as u64), record.clone()); + + self.rowid = Some(*rowid as u64); + self.current = Some(record.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } + } + // USE COMPOSITE KEYS LIKE (ROWID, OWNEDVALUE) this will facilitate things a LOT pub fn rewind(&mut self) -> Result> { - let rows = &self.table.rows; - - if let Some((&first_rowid, row_data)) = rows.iter().next() { - self.rowid = Some(first_rowid); - self.current = Some(OwnedRecord { - values: row_data.clone(), - }); - self.null_flag = false; - Ok(CursorResult::Ok(())) - } else { - self.rowid = None; - self.current = None; - self.null_flag = true; - Ok(CursorResult::Ok(())) + match &self.source { + Ephemeral::Table(table) => { + if let Some((&first_rowid, row_data)) = table.rows.iter().next() { + self.rowid = Some(first_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } + Ephemeral::Index(index) => { + if let Some(((_, rowid), row_data)) = index.rows.iter().next() { + self.rowid = Some(*rowid); + self.current = Some(row_data.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } } + + self.rowid = None; + self.current = None; + self.null_flag = true; + Ok(CursorResult::Ok(())) } pub fn last(&mut self) -> Result> { - let rows = &self.table.rows; - - if let Some((&last_rowid, row_data)) = rows.iter().next_back() { - self.rowid = Some(last_rowid); - self.current = Some(OwnedRecord { - values: row_data.clone(), - }); - self.null_flag = false; - Ok(CursorResult::Ok(())) - } else { - self.rowid = None; - self.current = None; - self.null_flag = true; - Ok(CursorResult::Ok(())) - } + match &self.source { + Ephemeral::Table(table) => { + if let Some((&last_rowid, row_data)) = table.rows.iter().next_back() { + self.rowid = Some(last_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } + Ephemeral::Index(index) => { + if let Some(((_, rowid), row_data)) = index.rows.iter().next_back() { + self.rowid = Some(*rowid); + self.current = Some(row_data.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } + }; + + self.rowid = None; + self.current = None; + self.null_flag = true; + Ok(CursorResult::Ok(())) } pub fn wait_for_completion(&mut self) -> Result<()> { @@ -185,25 +256,50 @@ impl EphemeralCursor { } pub fn next(&mut self) -> Result> { - let rows = &self.table.rows; - - if self.rowid.is_none() { - if let Some((&first_rowid, row_data)) = rows.iter().next() { - self.rowid = Some(first_rowid); - self.current = Some(OwnedRecord { - values: row_data.clone(), - }); - self.null_flag = false; - return Ok(CursorResult::Ok(())); + match &mut self.source { + Ephemeral::Table(table) => { + if self.rowid.is_none() { + if let Some((&first_rowid, row_data)) = table.rows.iter().next() { + self.rowid = Some(first_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } else if let Some(current_rowid) = self.rowid { + if let Some((&next_rowid, row_data)) = + table.rows.range((current_rowid + 1)..).next() + { + self.rowid = Some(next_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } } - } else if let Some(current_rowid) = self.rowid { - if let Some((&next_rowid, row_data)) = rows.range((current_rowid + 1)..).next() { - self.rowid = Some(next_rowid); - self.current = Some(OwnedRecord { - values: row_data.clone(), - }); - self.null_flag = false; - return Ok(CursorResult::Ok(())); + Ephemeral::Index(index) => { + if self.rowid.is_none() { + if let Some(((_, rowid), row_data)) = index.rows.iter().next() { + self.rowid = Some(*rowid); + self.current = Some(row_data.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } else if let Some(OwnedRecord { values }) = &self.current { + let key = values.first().expect("No values in index record"); + let mut iter = index.rows.range((key.clone(), 0)..); + iter.next(); // ignore first result since we don't support Exclude in OwnedValue. That would require the impl of Ord + if let Some(((_, rowid), row_data)) = iter.next() { + println!("{row_data:?}"); + self.rowid = Some(*rowid); + self.current = Some(row_data.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } } } @@ -214,25 +310,51 @@ impl EphemeralCursor { Ok(CursorResult::Ok(())) } pub fn prev(&mut self) -> Result> { - let rows = &self.table.rows; - - if self.rowid.is_none() { - if let Some((&first_rowid, row_data)) = rows.iter().next_back() { - self.rowid = Some(first_rowid); - self.current = Some(OwnedRecord { - values: row_data.clone(), - }); - self.null_flag = false; - return Ok(CursorResult::Ok(())); + match &self.source { + Ephemeral::Table(table) => { + if self.rowid.is_none() { + if let Some((&first_rowid, row_data)) = table.rows.iter().next_back() { + self.rowid = Some(first_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } else if let Some(current_rowid) = self.rowid { + if let Some((&next_rowid, row_data)) = + table.rows.range(..current_rowid).next_back() + { + self.rowid = Some(next_rowid); + self.current = Some(OwnedRecord { + values: row_data.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } } - } else if let Some(current_rowid) = self.rowid { - if let Some((&next_rowid, row_data)) = rows.range(..current_rowid).next_back() { - self.rowid = Some(next_rowid); - self.current = Some(OwnedRecord { - values: row_data.clone(), - }); - self.null_flag = false; - return Ok(CursorResult::Ok(())); + Ephemeral::Index(index) => { + if self.rowid.is_none() { + if let Some(((_, rowid), row_data)) = index.rows.iter().next_back() { + self.rowid = Some(*rowid); + self.current = Some(row_data.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } else if let Some(OwnedRecord { values }) = &self.current { + let key = values.first().expect("No values in index record"); + if let Some(((_, prev_rowid), row_data)) = index + .rows + .range(..(key.clone(), self.rowid.unwrap())) + .next_back() + { + self.rowid = Some(*prev_rowid); + self.current = Some(row_data.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(())); + } + } } } @@ -244,16 +366,44 @@ impl EphemeralCursor { } pub fn exists(&mut self, key: &OwnedValue) -> Result> { - let rows = &self.table.rows; - - for (rowid, row_data) in rows.iter() { - if row_data.contains(key) { - self.rowid = Some(*rowid); - self.current = Some(OwnedRecord { - values: row_data.clone(), - }); - self.null_flag = false; - return Ok(CursorResult::Ok(true)); + match &self.source { + Ephemeral::Table(table) => { + let OwnedValue::Integer(key) = key else { + return Err(LimboError::InternalError( + "btree tables are indexed by integers!".to_string(), + )); + }; + + if let Some(row) = table.rows.get(&(*key as u64)) { + self.rowid = Some(*key as u64); + self.current = Some(OwnedRecord { + values: row.clone(), + }); + self.null_flag = false; + return Ok(CursorResult::Ok(true)); + } + } + Ephemeral::Index(index) => { + let key = match key { + OwnedValue::Null | OwnedValue::Agg(_) | OwnedValue::Record(_) => { + return Err(LimboError::InternalError( + "Key of index cannot be Null, Agg nor Record".to_string(), + )); + } + OwnedValue::Integer(value) => OwnedValue::Integer(*value), + OwnedValue::Float(value) => OwnedValue::Float(*value), + OwnedValue::Text(value) => OwnedValue::Text(value.clone()), + OwnedValue::Blob(value) => OwnedValue::Blob(value.clone()), + }; + + let mut iter = index.rows.range((key.clone(), 0)..(key.clone(), u64::MAX)); + + if let Some(((_, rowid), row_data)) = iter.next() { + self.rowid = Some(*rowid); + self.current = Some(row_data.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(true)); + } } } @@ -267,427 +417,749 @@ impl EphemeralCursor { #[cfg(test)] mod tests { - use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; - - use crate::{ - schema::EphemeralTable, - types::{CursorResult, LimboText, OwnedRecord, OwnedValue, SeekKey, SeekOp}, - }; - - use super::EphemeralCursor; - - #[test] - fn test_next() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; - let val1 = vec![OwnedValue::Integer(42)]; - let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))]; - table.rows.insert(1, val1.clone()); - table.rows.insert(2, val2.clone()); - - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; - - cursor.next().unwrap(); // Move to the first row - assert_eq!( - cursor.current, - Some(OwnedRecord { - values: val1.clone() - }) - ); - - cursor.next().unwrap(); // Move to the second row - assert_eq!( - cursor.current, - Some(OwnedRecord { - values: val2.clone() - }) - ); - } - - #[test] - fn test_prev() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; - - let val1 = vec![OwnedValue::Integer(42)]; - let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))]; - table.rows.insert(1, val1.clone()); - table.rows.insert(2, val2.clone()); - - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; - - cursor.prev().unwrap(); // Should move to row 2 - assert_eq!( - cursor.current, - Some(OwnedRecord { - values: val2.clone() - }) - ); - - cursor.prev().unwrap(); // Should move to row 1 - assert_eq!( - cursor.current, - Some(OwnedRecord { - values: val1.clone() - }) - ); - - cursor.prev().unwrap(); // Should go out of bounds - assert!(cursor.current.is_none()); - assert!(cursor.null_flag); - } - - #[test] - fn test_last() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; - - let val1 = vec![OwnedValue::Integer(42)]; - let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))]; - table.rows.insert(1, val1.clone()); - table.rows.insert(2, val2.clone()); - - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; - - cursor.last().unwrap(); // Move to the last row - assert_eq!( - cursor.current, - Some(OwnedRecord { - values: val2.clone() - }) - ); - assert_eq!(cursor.rowid, Some(2)); - assert!(!cursor.null_flag); - } - - #[test] - fn test_last_empty_table() { - let table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; - - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; - - cursor.last().unwrap(); // Calling last on an empty table - assert!(cursor.current.is_none()); - assert!(cursor.null_flag); - assert!(cursor.rowid.is_none()); - } - - #[test] - fn test_rewind() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; - - let val1 = vec![OwnedValue::Integer(42)]; - let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))]; - table.rows.insert(1, val1.clone()); - table.rows.insert(2, val2.clone()); - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; - - cursor.rewind().unwrap(); // Move to the first row - assert_eq!( - cursor.current, - Some(OwnedRecord { - values: val1.clone() - }) - ); - assert_eq!(cursor.rowid, Some(1)); - assert!(!cursor.null_flag); - } - - #[test] - fn test_rewind_empty_table() { - let table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; + mod test_table { + use std::{collections::BTreeMap, rc::Rc}; - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, + use crate::{ + ephemeral::{Ephemeral, EphemeralCursor}, + schema::EphemeralTable, + types::{CursorResult, LimboText, OwnedRecord, OwnedValue, SeekKey, SeekOp}, }; - cursor.rewind().unwrap(); // Calling rewind on an empty table - assert!(cursor.current.is_none()); - assert!(cursor.null_flag); - assert!(cursor.rowid.is_none()); - } - - #[test] - fn test_exists_key_found() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; + #[test] + fn test_next() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + let val1 = vec![OwnedValue::Integer(42)]; + let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))]; + table.rows.insert(1, val1.clone()); + table.rows.insert(2, val2.clone()); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.next().unwrap(); // Move to the first row + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val1.clone() + }) + ); - let val1 = OwnedValue::Integer(42); - let val2 = OwnedValue::Text(LimboText::new(Rc::new("Hello".to_string()))); - table.rows.insert(1, vec![val1.clone()]); - table.rows.insert(2, vec![val2.clone()]); + cursor.next().unwrap(); // Move to the second row + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val2.clone() + }) + ); + } - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; + #[test] + fn test_prev() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; - let result = cursor.exists(&val1).unwrap(); - assert_eq!(result, CursorResult::Ok(true)); - assert_eq!(cursor.rowid, Some(1)); - assert_eq!( - cursor.current, - Some(OwnedRecord { - values: vec![val1.clone()] - }) - ); - assert!(!cursor.null_flag); - } + let val1 = vec![OwnedValue::Integer(42)]; + let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))]; + table.rows.insert(1, val1.clone()); + table.rows.insert(2, val2.clone()); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.prev().unwrap(); // Should move to row 2 + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val2.clone() + }) + ); - #[test] - fn test_exists_key_not_found() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; + cursor.prev().unwrap(); // Should move to row 1 + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val1.clone() + }) + ); - let val1 = OwnedValue::Integer(42); - let val2 = OwnedValue::Text(LimboText::new(Rc::new("Hello".to_string()))); - table.rows.insert(1, vec![val1.clone()]); - table.rows.insert(2, vec![val2.clone()]); + cursor.prev().unwrap(); // Should go out of bounds + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + } - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; + #[test] + fn test_last() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; - let result = cursor.exists(&OwnedValue::Integer(99)).unwrap(); - assert_eq!(result, CursorResult::Ok(false)); - assert!(cursor.rowid.is_none()); - assert!(cursor.current.is_none()); - assert!(cursor.null_flag); - } + let val1 = vec![OwnedValue::Integer(42)]; + let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))]; + table.rows.insert(1, val1.clone()); + table.rows.insert(2, val2.clone()); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.last().unwrap(); // Move to the last row + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val2.clone() + }) + ); + assert_eq!(cursor.rowid, Some(2)); + assert!(!cursor.null_flag); + } - #[test] - fn test_insert_new_row() { - let table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; + #[test] + fn test_last_empty_table() { + let table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.last().unwrap(); // Calling last on an empty table + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + assert!(cursor.rowid.is_none()); + } - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; + #[test] + fn test_rewind() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; - let key = OwnedValue::Integer(1); - let record = OwnedRecord { - values: vec![OwnedValue::Text(LimboText::new(Rc::new( + let val1 = vec![OwnedValue::Integer(42)]; + let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( "Hello".to_string(), - )))], - }; - - cursor.insert(&key, &record, false).unwrap(); + )))]; + table.rows.insert(1, val1.clone()); + table.rows.insert(2, val2.clone()); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.rewind().unwrap(); // Move to the first row + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: val1.clone() + }) + ); + assert_eq!(cursor.rowid, Some(1)); + assert!(!cursor.null_flag); + } - assert_eq!(cursor.table.rows.len(), 1); - assert_eq!(cursor.table.rows.get(&1), Some(&record.values)); - assert_eq!(cursor.rowid, Some(1)); - assert_eq!(cursor.current, Some(record)); - assert!(!cursor.null_flag); - } + #[test] + fn test_rewind_empty_table() { + let table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.rewind().unwrap(); // Calling rewind on an empty table + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + assert!(cursor.rowid.is_none()); + } - #[test] - fn test_insert_overwrite_row() { - let table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; + #[test] + fn test_exists_key_found() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let val1 = OwnedValue::Integer(42); + let val2 = OwnedValue::Text(LimboText::new(Rc::new("Hello".to_string()))); + table.rows.insert(1, vec![val1.clone()]); + table.rows.insert(2, vec![val2.clone()]); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + let result = cursor.exists(&OwnedValue::Integer(1)).unwrap(); + assert_eq!(result, CursorResult::Ok(true)); + assert_eq!(cursor.rowid, Some(1)); + assert_eq!( + cursor.current, + Some(OwnedRecord { + values: vec![val1.clone()] + }) + ); + assert!(!cursor.null_flag); + } - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; + #[test] + fn test_exists_key_not_found() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let val1 = OwnedValue::Integer(42); + let val2 = OwnedValue::Text(LimboText::new(Rc::new("Hello".to_string()))); + table.rows.insert(1, vec![val1.clone()]); + table.rows.insert(2, vec![val2.clone()]); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + let result = cursor.exists(&OwnedValue::Integer(99)).unwrap(); + assert_eq!(result, CursorResult::Ok(false)); + assert!(cursor.rowid.is_none()); + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + } - let key = OwnedValue::Integer(1); - let record1 = OwnedRecord { - values: vec![OwnedValue::Text(LimboText::new(Rc::new( - "First".to_string(), - )))], - }; - let record2 = OwnedRecord { - values: vec![OwnedValue::Text(LimboText::new(Rc::new( - "Second".to_string(), - )))], - }; + #[test] + fn test_insert_new_row() { + let table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + let key = OwnedValue::Integer(1); + let record = OwnedRecord { + values: vec![OwnedValue::Text(LimboText::new(Rc::new( + "Hello".to_string(), + )))], + }; + + cursor.insert(&key, &record, false).unwrap(); + if let Ephemeral::Table(table) = cursor.source { + assert_eq!(table.rows.len(), 1); + assert_eq!(table.rows.get(&1), Some(&record.values)); + assert_eq!(cursor.rowid, Some(1)); + assert_eq!(cursor.current, Some(record)); + assert!(!cursor.null_flag); + } + } - cursor.insert(&key, &record1, false).unwrap(); - cursor.insert(&key, &record2, true).unwrap(); + #[test] + fn test_insert_overwrite_row() { + let table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + let key = OwnedValue::Integer(1); + let record1 = OwnedRecord { + values: vec![OwnedValue::Text(LimboText::new(Rc::new( + "First".to_string(), + )))], + }; + let record2 = OwnedRecord { + values: vec![OwnedValue::Text(LimboText::new(Rc::new( + "Second".to_string(), + )))], + }; + + cursor.insert(&key, &record1, false).unwrap(); + cursor.insert(&key, &record2, true).unwrap(); + + if let Ephemeral::Table(table) = cursor.source { + assert_eq!(table.rows.len(), 1); + assert_eq!(table.rows.get(&1), Some(&record2.values)); + assert_eq!(cursor.rowid, Some(1)); + assert_eq!(cursor.current, Some(record2)); + assert!(!cursor.null_flag); + } + } - assert_eq!(cursor.table.rows.len(), 1); - assert_eq!(cursor.table.rows.get(&1), Some(&record2.values)); - assert_eq!(cursor.rowid, Some(1)); - assert_eq!(cursor.current, Some(record2)); - assert!(!cursor.null_flag); + #[test] + fn test_do_seek_by_rowid_eq() { + let mut table = EphemeralTable { + rows: BTreeMap::new(), + next_rowid: 1, + columns: vec![], + }; + + table.rows.insert(1, vec![OwnedValue::Integer(10)]); + table.rows.insert(2, vec![OwnedValue::Integer(20)]); + table.rows.insert(3, vec![OwnedValue::Integer(30)]); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Table(table), + rowid: None, + current: None, + null_flag: true, + }; + + let result = cursor.do_seek(SeekKey::TableRowId(2), SeekOp::EQ).unwrap(); + assert_eq!( + result, + CursorResult::Ok(( + Some(2), + Some(OwnedRecord { + values: vec![OwnedValue::Integer(20)] + }) + )) + ); + assert_eq!(cursor.rowid, Some(2)); + assert!(!cursor.null_flag); + } } - #[test] - fn test_do_seek_by_rowid_eq() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; - - table.rows.insert(1, vec![OwnedValue::Integer(10)]); - table.rows.insert(2, vec![OwnedValue::Integer(20)]); - table.rows.insert(3, vec![OwnedValue::Integer(30)]); + mod test_index { + use std::{collections::BTreeMap, rc::Rc}; - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, + use crate::{ + ephemeral::{Ephemeral, EphemeralCursor}, + schema::EphemeralIndex, + types::{CursorResult, LimboText, OwnedRecord, OwnedValue, SeekOp}, + LimboError, }; - let result = cursor.do_seek(SeekKey::TableRowId(2), SeekOp::EQ).unwrap(); - assert_eq!( - result, - CursorResult::Ok(( - Some(2), - Some(OwnedRecord { - values: vec![OwnedValue::Integer(20)] - }) - )) - ); - assert_eq!(cursor.rowid, Some(2)); - assert!(!cursor.null_flag); - } + #[test] + fn test_next() { + let mut table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + let val1 = OwnedRecord { + values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], + }; + + let val2 = OwnedRecord { + values: vec![ + OwnedValue::Integer(44), + OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + ], + }; + + table + .rows + .insert((OwnedValue::Integer(42), 1), val1.clone()); + table + .rows + .insert((OwnedValue::Integer(44), 2), val2.clone()); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.next().unwrap(); // Move to the first row + assert_eq!(cursor.current, Some(val1)); + + cursor.next().unwrap(); // Move to the second row + assert_eq!(cursor.current, Some(val2)); + } - #[test] - fn test_do_seek_by_index_key_ge() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; + #[test] + fn test_prev() { + let mut table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + let val1 = OwnedRecord { + values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], + }; + + let val2 = OwnedRecord { + values: vec![ + OwnedValue::Integer(44), + OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + ], + }; + + table + .rows + .insert((OwnedValue::Integer(42), 1), val1.clone()); + table + .rows + .insert((OwnedValue::Integer(44), 1), val2.clone()); + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.prev().unwrap(); // Should move to row 2 + assert_eq!(cursor.current, Some(val2)); + + cursor.prev().unwrap(); // Should move to row 1 + assert_eq!(cursor.current, Some(val1)); + + cursor.prev().unwrap(); // Should go out of bounds + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + } - table.rows.insert(1, vec![OwnedValue::Integer(10)]); - table.rows.insert(2, vec![OwnedValue::Integer(20)]); - table.rows.insert(3, vec![OwnedValue::Integer(30)]); + #[test] + fn test_last() { + let mut table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + let val1 = OwnedRecord { + values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], + }; + + let val2 = OwnedRecord { + values: vec![ + OwnedValue::Integer(44), + OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + ], + }; + + table + .rows + .insert((OwnedValue::Integer(42), 1), val1.clone()); + table + .rows + .insert((OwnedValue::Integer(44), 2), val2.clone()); + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.last().unwrap(); // Move to the last row + assert_eq!(cursor.current, Some(val2)); + assert_eq!(cursor.rowid, Some(2)); + assert!(!cursor.null_flag); + } - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; + #[test] + fn test_last_empty_table() { + let table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.last().unwrap(); // Calling last on an empty table + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + assert!(cursor.rowid.is_none()); + } - let key = OwnedRecord { - values: vec![OwnedValue::Integer(25)], - }; + #[test] + fn test_rewind() { + let mut table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + + let val1 = OwnedRecord { + values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], + }; + + let val2 = OwnedRecord { + values: vec![ + OwnedValue::Integer(44), + OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + ], + }; + table + .rows + .insert((OwnedValue::Integer(42), 1), val1.clone()); + table + .rows + .insert((OwnedValue::Integer(44), 2), val2.clone()); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.rewind().unwrap(); // Move to the first row + assert_eq!(cursor.current, Some(val1)); + assert_eq!(cursor.rowid, Some(1)); + assert!(!cursor.null_flag); + } - let result = cursor.do_seek(SeekKey::IndexKey(&key), SeekOp::GE).unwrap(); - assert_eq!( - result, - CursorResult::Ok(( - Some(3), - Some(OwnedRecord { - values: vec![OwnedValue::Integer(30)] - }) - )) - ); - assert_eq!(cursor.rowid, Some(3)); - assert!(!cursor.null_flag); - } + #[test] + fn test_rewind_empty_table() { + let table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor.rewind().unwrap(); // Calling rewind on an empty table + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + assert!(cursor.rowid.is_none()); + } - #[test] - fn test_do_seek_no_match() { - let mut table = EphemeralTable { - rows: BTreeMap::new(), - next_rowid: 1, - columns: vec![], - }; + #[test] + fn test_exists_key_found() { + let mut table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + let val1 = OwnedRecord { + values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], + }; + let val2 = OwnedRecord { + values: vec![ + OwnedValue::Integer(44), + OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + ], + }; + + table + .rows + .insert((OwnedValue::Integer(42), 1), val1.clone()); + table + .rows + .insert((OwnedValue::Integer(44), 2), val2.clone()); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + let result = cursor.exists(&OwnedValue::Integer(44)).unwrap(); + assert_eq!(result, CursorResult::Ok(true)); + assert_eq!(cursor.rowid, Some(2)); + assert_eq!(cursor.current, Some(val2)); + assert!(!cursor.null_flag); + } - table.rows.insert(1, vec![OwnedValue::Integer(10)]); - table.rows.insert(2, vec![OwnedValue::Integer(20)]); - table.rows.insert(3, vec![OwnedValue::Integer(30)]); + #[test] + fn test_exists_key_not_found() { + let mut table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + let val1 = OwnedRecord { + values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], + }; + + let val2 = OwnedRecord { + values: vec![ + OwnedValue::Integer(44), + OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + ], + }; + + table + .rows + .insert((OwnedValue::Integer(42), 1), val1.clone()); + table + .rows + .insert((OwnedValue::Integer(44), 2), val2.clone()); + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + let result = cursor.exists(&OwnedValue::Integer(23)).unwrap(); + assert_eq!(result, CursorResult::Ok(false)); + assert!(cursor.rowid.is_none()); + assert!(cursor.current.is_none()); + assert!(cursor.null_flag); + } - let mut cursor = EphemeralCursor { - table, - rowid: None, - current: None, - null_flag: true, - }; + #[test] + fn test_insert_new_row() { + let table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + let record = OwnedRecord { + values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + cursor + .insert(&OwnedValue::Integer(1), &record, false) + .unwrap(); + + if let Ephemeral::Index(table) = cursor.source { + assert_eq!(table.rows.len(), 1); + let expected_key = (OwnedValue::Integer(42), 1); + assert_eq!(table.rows.get(&expected_key), Some(&record)); + assert_eq!(cursor.rowid, Some(1)); + assert_eq!(cursor.current, Some(record)); + assert!(!cursor.null_flag); + } + } - let key = OwnedRecord { - values: vec![OwnedValue::Integer(40)], - }; + #[test] + fn test_insert_dont_overwrite_row() { + let table = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(table), + rowid: None, + current: None, + null_flag: true, + }; + + let key = OwnedValue::Integer(1); + let record1 = OwnedRecord { + values: vec![OwnedValue::Text(LimboText::new(Rc::new( + "First".to_string(), + )))], + }; + + let id = OwnedValue::Text(LimboText::new(Rc::new("Second".to_string()))); + let record2 = OwnedRecord { + values: vec![id.clone()], + }; + + cursor.insert(&key, &record1, false).unwrap(); + cursor.insert(&key, &record2, true).unwrap(); + + if let Ephemeral::Index(table) = cursor.source { + assert_eq!(table.rows.len(), 2); + let expected_key = (id, 1); + assert_eq!(table.rows.get(&expected_key), Some(&record2)); + assert_eq!(cursor.rowid, Some(1)); + assert_eq!(cursor.current, Some(record2)); + assert!(!cursor.null_flag); + } + } - let result = cursor.do_seek(SeekKey::IndexKey(&key), SeekOp::EQ).unwrap(); - assert_eq!(result, CursorResult::Ok((None, None))); - assert_eq!(cursor.rowid, None); - assert!(cursor.null_flag); + #[test] + fn test_index_key_type_consistency() { + let index = EphemeralIndex { + rows: BTreeMap::new(), + columns: vec![], + }; + + let mut cursor = EphemeralCursor { + source: Ephemeral::Index(index), + rowid: None, + current: None, + null_flag: true, + }; + + // First insert: integer key + let record1 = OwnedRecord { + values: vec![OwnedValue::Integer(42)], + }; + let result1 = cursor.insert(&OwnedValue::Integer(1), &record1, false); + assert!(result1.is_ok(), "First insertion should succeed"); + + // Second insert: text key (should fail) + let record2 = OwnedRecord { + values: vec![OwnedValue::Text(LimboText::new(Rc::new( + "Invalid".to_string(), + )))], + }; + let result2 = cursor.insert(&OwnedValue::Integer(2), &record2, false); + + assert!( + matches!(result2, Err(LimboError::InternalError(_))), + "Mismatched key type should result in an InternalError" + ); + } } } diff --git a/core/schema.rs b/core/schema.rs index 4771f755b..d4d6f77d9 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -1,4 +1,5 @@ use crate::types::OwnedValue; +use crate::types::{OwnedRecord, OwnedValue}; use crate::VirtualTable; use crate::{util::normalize_ident, Result}; use core::fmt; @@ -948,6 +949,25 @@ mod tests { Ok(()) } } +#[derive(Debug)] +pub struct EphemeralIndex { + pub rows: BTreeMap, // rowid -> each index in the Vec is a column + pub columns: Vec, // columns +} + +impl EphemeralIndex { + pub fn new() -> Self { + Self { + rows: BTreeMap::new(), + columns: vec![], + } + } +} + +// What defines row uniqueness is the combination of the values of the first column with +// the rowid. In this way, we can have multiple rows pointing to the same rowid. +// https://www.sqlite.org/queryplanner.html +type IndexId = (OwnedValue, u64); #[derive(Debug)] pub struct EphemeralTable { diff --git a/core/types.rs b/core/types.rs index 8ebd97680..c24216a80 100644 --- a/core/types.rs +++ b/core/types.rs @@ -671,7 +671,8 @@ pub enum Cursor { Table(BTreeCursor), Index(BTreeCursor), Pseudo(PseudoCursor), - Ephemeral(EphemeralCursor), + EphemeralTable(EphemeralCursor), + EphemeralIndex(EphemeralCursor), Sorter(Sorter), Virtual(VTabOpaqueCursor), } @@ -693,8 +694,12 @@ impl Cursor { Self::Sorter(cursor) } - pub fn new_ephemeral(cursor: EphemeralCursor) -> Self { - Self::Ephemeral(cursor) + pub fn new_ephemeral_table(cursor: EphemeralCursor) -> Self { + Self::EphemeralTable(cursor) + } + + pub fn new_ephemeral_index(cursor: EphemeralCursor) -> Self { + Self::EphemeralIndex(cursor) } pub fn as_table_mut(&mut self) -> &mut BTreeCursor { @@ -732,10 +737,17 @@ impl Cursor { } } - pub fn as_ephemeral_mut(&mut self) -> &mut EphemeralCursor { + pub fn as_ephemeral_table_mut(&mut self) -> &mut EphemeralCursor { + match self { + Self::EphemeralTable(cursor) => cursor, + _ => panic!("Cursor is not ephemeral table"), + } + } + + pub fn as_ephemeral_index_mut(&mut self) -> &mut EphemeralCursor { match self { - Self::Ephemeral(cursor) => cursor, - _ => panic!("Cursor is not ephemeral"), + Self::EphemeralIndex(cursor) => cursor, + _ => panic!("Cursor is not ephemeral index"), } } } diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index d8250be73..834d8ca58 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ parameters::Parameters, - schema::{BTreeTable, EphemeralTable, Index, PseudoTable}, + schema::{BTreeTable, EphemeralIndex, EphemeralTable, Index, PseudoTable}, storage::sqlite3_ondisk::DatabaseHeader, translate::plan::{ResultSetColumn, TableReference}, Connection, VirtualTable, @@ -39,7 +39,8 @@ pub enum CursorType { BTreeTable(Rc), BTreeIndex(Rc), Pseudo(Rc), - Ephemeral(Rc), + EphemeralTable(Rc), + EphemeralIndex(Rc), Sorter, VirtualTable(Rc), } diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 5337eb957..d603fa0fe 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -478,8 +478,13 @@ pub fn insn_to_str( let name = pseudo_table.columns.get(*column).unwrap().name.as_ref(); name } - CursorType::Ephemeral(ephemeral_table) => { - Some(&ephemeral_table.columns.get(*column).unwrap().name) + CursorType::EphemeralTable(table) => { + let name = table.columns.get(*column).unwrap().name.as_ref(); + name + } + CursorType::EphemeralIndex(index) => { + let name = index.columns.get(*column).unwrap().name.as_ref(); + name } CursorType::Sorter => None, CursorType::VirtualTable(v) => v.columns.get(*column).unwrap().name.as_ref(), @@ -1237,16 +1242,15 @@ pub fn insn_to_str( ), Insn::OpenEphemeral { cursor_id, - content_reg, - num_fields, + is_btree, } => ( "OpenEphemeral", *cursor_id as i32, - *content_reg as i32, - *num_fields as i32, + *is_btree as i32, + 0, OwnedValue::build_text(Rc::new("".to_string())), 0, - format!("{} columns in r[{}]", num_fields, content_reg), + format!("Operate as BTreeTable: {}", is_btree), ), }; format!( diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 92e161d27..b566f6d21 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -642,8 +642,7 @@ pub enum Insn { /// Open a new cursor P1 to a transient table. OpenEphemeral { cursor_id: CursorID, - content_reg: usize, - num_fields: usize, + is_btree: bool, }, } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 701a3a2b1..5bef97f68 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -282,7 +282,7 @@ fn get_cursor_as_virtual_mut<'long, 'short>( cursor } -fn get_cursor_as_ephemeral_mut<'long, 'short>( +fn get_cursor_as_ephemeral_table_mut<'long, 'short>( cursors: &'short mut RefMut<'long, Vec>>, cursor_id: CursorID, ) -> &'short mut EphemeralCursor { @@ -291,7 +291,19 @@ fn get_cursor_as_ephemeral_mut<'long, 'short>( .expect("cursor id out of bounds") .as_mut() .expect("cursor not allocated") - .as_ephemeral_mut() + .as_ephemeral_table_mut() +} + +fn get_cursor_as_ephemeral_index_mut<'long, 'short>( + cursors: &'short mut RefMut<'long, Vec>>, + cursor_id: CursorID, +) -> &'short mut EphemeralCursor { + cursors + .get_mut(cursor_id) + .expect("cursor id out of bounds") + .as_mut() + .expect("cursor not allocated") + .as_ephemeral_index_mut() } struct Bitfield([u64; N]); @@ -416,9 +428,12 @@ macro_rules! must_be_general_cursor { CursorType::BTreeIndex(_) => { GeneralCursor::BTree(get_cursor_as_index_mut(&mut $cursors, $cursor_id)) } - CursorType::Ephemeral(_) => { - GeneralCursor::Ephemeral(get_cursor_as_ephemeral_mut(&mut $cursors, $cursor_id)) - } + CursorType::EphemeralTable(_) => GeneralCursor::Ephemeral( + get_cursor_as_ephemeral_table_mut(&mut $cursors, $cursor_id), + ), + CursorType::EphemeralIndex(_) => GeneralCursor::Ephemeral( + get_cursor_as_ephemeral_index_mut(&mut $cursors, $cursor_id), + ), CursorType::Pseudo(_) => panic!("{} on pseudo cursor", $insn_name), CursorType::Sorter => panic!("{} on sorter cursor", $insn_name), CursorType::VirtualTable(_) => panic!("{} on virtual table cursor", $insn_name), @@ -874,11 +889,15 @@ impl Program { .unwrap() .replace(Cursor::new_index(cursor)); } - CursorType::Ephemeral(_) => { - cursors - .get_mut(*cursor_id) - .unwrap() - .replace(Cursor::new_ephemeral(EphemeralCursor::new())); + CursorType::EphemeralTable(_) => { + cursors.get_mut(*cursor_id).unwrap().replace( + Cursor::new_ephemeral_table(EphemeralCursor::new_with_table()), + ); + } + CursorType::EphemeralIndex(_) => { + cursors.get_mut(*cursor_id).unwrap().replace( + Cursor::new_ephemeral_table(EphemeralCursor::new_with_index()), + ); } CursorType::Pseudo(_) => { panic!("OpenReadAsync on pseudo cursor"); @@ -974,15 +993,25 @@ impl Program { } Insn::OpenEphemeral { cursor_id, - content_reg: _, - num_fields: _, + is_btree, } => { let mut cursors = state.cursors.borrow_mut(); - let cursor = EphemeralCursor::new(); - cursors - .get_mut(*cursor_id) - .unwrap() - .replace(Cursor::new_ephemeral(cursor)); + + if *is_btree { + cursors + .get_mut(*cursor_id) + .unwrap() + .replace(Cursor::new_ephemeral_table( + EphemeralCursor::new_with_table(), + )); + } else { + cursors + .get_mut(*cursor_id) + .unwrap() + .replace(Cursor::new_ephemeral_index( + EphemeralCursor::new_with_index(), + )); + } state.pc += 1; } Insn::RewindAsync { cursor_id } => { @@ -1014,15 +1043,6 @@ impl Program { state.pc += 1; } - Insn::LastAsync { cursor_id } => { - let mut cursors = state.cursors.borrow_mut(); - match must_be_general_cursor!(*cursor_id, self.cursor_ref, cursors, "LastAsync") - { - GeneralCursor::BTree(cursor) => return_if_io!(cursor.last()), - GeneralCursor::Ephemeral(cursor) => return_if_io!(cursor.last()), - } - state.pc += 1; - } Insn::LastAwait { cursor_id, pc_if_empty, @@ -1103,7 +1123,8 @@ impl Program { match cursor_type { CursorType::BTreeTable(_) | CursorType::BTreeIndex(_) - | CursorType::Ephemeral(_) => { + | CursorType::EphemeralTable(_) + | CursorType::EphemeralIndex(_) => { match must_be_general_cursor!( *cursor_id, self.cursor_ref, From 956ca4d5f41c6a72f7a727fe3ad49b0cc06cbdec Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 5 Feb 2025 14:05:55 -0300 Subject: [PATCH 15/17] Makes clippy happy --- core/ephemeral.rs | 2 +- core/storage/btree.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index 135931981..c93be5199 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -786,7 +786,7 @@ mod tests { use crate::{ ephemeral::{Ephemeral, EphemeralCursor}, schema::EphemeralIndex, - types::{CursorResult, LimboText, OwnedRecord, OwnedValue, SeekOp}, + types::{CursorResult, LimboText, OwnedRecord, OwnedValue}, LimboError, }; diff --git a/core/storage/btree.rs b/core/storage/btree.rs index cb916a95e..65878ebe5 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -1769,6 +1769,7 @@ impl BTreeCursor { Ok(CursorResult::Ok(())) } + #[allow(dead_code)] pub fn last(&mut self) -> Result> { match self.move_to_rightmost()? { CursorResult::Ok(_) => self.prev(), From d107f4d71351765fbfacfc631be79a3683d965ed Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 5 Feb 2025 23:05:11 -0300 Subject: [PATCH 16/17] Change EphemeralIndex structure to accept multiple values in index --- core/ephemeral.rs | 394 ++++++++++++++++++++++++---------------------- core/schema.rs | 14 +- 2 files changed, 215 insertions(+), 193 deletions(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index c93be5199..cda0bd8cb 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -73,20 +73,35 @@ impl EphemeralCursor { unreachable!("index seek key should be a record"); }; - let rows = &index.rows; - let index = index_key.values.first().expect("No values in index record"); + let search_key = index_key.clone(); + + // Exclude the last value for comparison + let trimmed_key = OwnedRecord { + values: search_key.values[..search_key.values.len() - 1].to_vec(), + }; + let rows = &index.rows; let mut range = match op { - SeekOp::EQ => rows.range((index.clone(), 0)..=(index.clone(), u64::MAX)), - SeekOp::GE => rows.range((index.clone(), 0)..), - SeekOp::GT => rows.range((index.clone(), 0)..), // To exclude index we need to implement Ord for OwnedValue + SeekOp::EQ => rows.range(trimmed_key.clone()..=trimmed_key.clone()), + SeekOp::GE => rows.range(trimmed_key.clone()..), + SeekOp::GT => rows.range(trimmed_key.clone()..), }; - if let Some((index, row)) = range.next() { - self.rowid = Some(index.1); - self.current = Some(row.clone()); + // this is an obvious makeshift but I didn't find any better way to do it + if matches!(op, SeekOp::GT) { + range.next(); + } + + if let Some(record) = range.next() { + let rowid = match record.values.last() { + Some(OwnedValue::Integer(rowid)) => *rowid as u64, + _ => unreachable!("index records should have an integer rowid"), + }; + + self.rowid = Some(rowid); + self.current = Some(record.clone()); self.null_flag = false; - return Ok(CursorResult::Ok((self.rowid, self.current.clone()))); + return Ok(CursorResult::Ok((Some(rowid), self.current.clone()))); } } } @@ -107,7 +122,6 @@ impl EphemeralCursor { match &mut self.source { Ephemeral::Table(table) => { let rowid = if moved_before { - // Traverse to find the correct position (here, just use `key` as rowid for simplicity) if let OwnedValue::Integer(rowid) = key { *rowid as u64 } else { @@ -116,16 +130,13 @@ impl EphemeralCursor { )); } } else { - // Use the next available rowid let rowid = table.next_rowid; table.next_rowid += 1; rowid }; - // Insert the record into the table table.rows.insert(rowid, record.values.clone()); - // Update cursor state self.rowid = Some(rowid); self.current = Some(record.clone()); self.null_flag = false; @@ -138,36 +149,14 @@ impl EphemeralCursor { "Invalid key type for rowid".to_string(), )); }; + let mut record = record.clone(); - let key = match record.values.first().expect("No values in index record") { - OwnedValue::Null | OwnedValue::Agg(_) | OwnedValue::Record(_) => { - return Err(LimboError::InternalError( - "Key of index cannot be Null, Agg nor Record".to_string(), - )); - } - OwnedValue::Integer(value) => OwnedValue::Integer(*value), - OwnedValue::Float(value) => OwnedValue::Float(*value), - OwnedValue::Text(value) => OwnedValue::Text(value.clone()), - OwnedValue::Blob(value) => OwnedValue::Blob(value.clone()), - }; + index + .rows + .retain(|r| !r.values.contains(&OwnedValue::Integer(*rowid))); - // Check existing index entries for type consistency - // TODO: probably this should be inside the EphemeralIndex - if let Some((existing_key, _)) = index.rows.iter().next() { - if !matches!( - (existing_key, &key), - ((OwnedValue::Integer(_), _), OwnedValue::Integer(_)) - | ((OwnedValue::Float(_), _), OwnedValue::Float(_)) - | ((OwnedValue::Text(_), _), OwnedValue::Text(_)) - | ((OwnedValue::Blob(_), _), OwnedValue::Blob(_)) - ) { - return Err(LimboError::InternalError( - "Mismatched key type in index".to_string(), - )); - } - } - - index.rows.insert((key, *rowid as u64), record.clone()); + record.values.push(OwnedValue::Integer(*rowid)); + index.rows.insert(record.clone()); self.rowid = Some(*rowid as u64); self.current = Some(record.clone()); @@ -177,7 +166,7 @@ impl EphemeralCursor { } } } - // USE COMPOSITE KEYS LIKE (ROWID, OWNEDVALUE) this will facilitate things a LOT + pub fn rewind(&mut self) -> Result> { match &self.source { Ephemeral::Table(table) => { @@ -191,9 +180,14 @@ impl EphemeralCursor { } } Ephemeral::Index(index) => { - if let Some(((_, rowid), row_data)) = index.rows.iter().next() { - self.rowid = Some(*rowid); - self.current = Some(row_data.clone()); + if let Some(row) = index.rows.iter().next() { + let OwnedValue::Integer(rowid) = row.values.last().unwrap() else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + }; + self.rowid = Some(*rowid as u64); + self.current = Some(row.clone()); self.null_flag = false; return Ok(CursorResult::Ok(())); } @@ -219,9 +213,15 @@ impl EphemeralCursor { } } Ephemeral::Index(index) => { - if let Some(((_, rowid), row_data)) = index.rows.iter().next_back() { - self.rowid = Some(*rowid); - self.current = Some(row_data.clone()); + if let Some(row) = index.rows.iter().next_back() { + let OwnedValue::Integer(rowid) = row.values.last().unwrap() else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + }; + + self.rowid = Some(*rowid as u64); + self.current = Some(row.clone()); self.null_flag = false; return Ok(CursorResult::Ok(())); } @@ -282,20 +282,28 @@ impl EphemeralCursor { } Ephemeral::Index(index) => { if self.rowid.is_none() { - if let Some(((_, rowid), row_data)) = index.rows.iter().next() { - self.rowid = Some(*rowid); - self.current = Some(row_data.clone()); + if let Some(row) = index.rows.iter().next() { + let OwnedValue::Integer(rowid) = row.values.last().unwrap() else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + }; + self.rowid = Some(*rowid as u64); + self.current = Some(row.clone()); self.null_flag = false; return Ok(CursorResult::Ok(())); } - } else if let Some(OwnedRecord { values }) = &self.current { - let key = values.first().expect("No values in index record"); - let mut iter = index.rows.range((key.clone(), 0)..); - iter.next(); // ignore first result since we don't support Exclude in OwnedValue. That would require the impl of Ord - if let Some(((_, rowid), row_data)) = iter.next() { - println!("{row_data:?}"); - self.rowid = Some(*rowid); - self.current = Some(row_data.clone()); + } else if let Some(current) = &self.current { + let mut iter = index.rows.range(current..); + iter.next(); // ignore first result since we don't support Exclude in OwnedRecord. That would require the impl of RangeBound + if let Some(row) = iter.next() { + let OwnedValue::Integer(rowid) = row.values.last().unwrap() else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + }; + self.rowid = Some(*rowid as u64); + self.current = Some(row.clone()); self.null_flag = false; return Ok(CursorResult::Ok(())); } @@ -336,21 +344,28 @@ impl EphemeralCursor { } Ephemeral::Index(index) => { if self.rowid.is_none() { - if let Some(((_, rowid), row_data)) = index.rows.iter().next_back() { - self.rowid = Some(*rowid); - self.current = Some(row_data.clone()); + if let Some(row) = index.rows.iter().next_back() { + let OwnedValue::Integer(rowid) = row.values.last().unwrap() else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + }; + self.rowid = Some(*rowid as u64); + self.current = Some(row.clone()); self.null_flag = false; return Ok(CursorResult::Ok(())); } - } else if let Some(OwnedRecord { values }) = &self.current { - let key = values.first().expect("No values in index record"); - if let Some(((_, prev_rowid), row_data)) = index - .rows - .range(..(key.clone(), self.rowid.unwrap())) - .next_back() - { - self.rowid = Some(*prev_rowid); - self.current = Some(row_data.clone()); + } else if let Some(current) = &self.current { + if let Some(prev_record) = index.rows.range(..current).next_back() { + let OwnedValue::Integer(prev_rowid) = prev_record.values.last().unwrap() + else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + }; + + self.rowid = Some(*prev_rowid as u64); + self.current = Some(prev_record.clone()); self.null_flag = false; return Ok(CursorResult::Ok(())); } @@ -384,25 +399,27 @@ impl EphemeralCursor { } } Ephemeral::Index(index) => { - let key = match key { - OwnedValue::Null | OwnedValue::Agg(_) | OwnedValue::Record(_) => { - return Err(LimboError::InternalError( - "Key of index cannot be Null, Agg nor Record".to_string(), - )); - } - OwnedValue::Integer(value) => OwnedValue::Integer(*value), - OwnedValue::Float(value) => OwnedValue::Float(*value), - OwnedValue::Text(value) => OwnedValue::Text(value.clone()), - OwnedValue::Blob(value) => OwnedValue::Blob(value.clone()), + let search_key = match key { + OwnedValue::Record(record) => record.clone(), + _ => OwnedRecord { + values: vec![key.clone()], + }, }; - let mut iter = index.rows.range((key.clone(), 0)..(key.clone(), u64::MAX)); - - if let Some(((_, rowid), row_data)) = iter.next() { - self.rowid = Some(*rowid); - self.current = Some(row_data.clone()); - self.null_flag = false; - return Ok(CursorResult::Ok(true)); + let mut iter = index.rows.range(search_key.clone()..); + if let Some(record) = iter.next() { + if record.values.contains(&key) { + let OwnedValue::Integer(rowid) = record.values.last().unwrap() else { + return Err(LimboError::InternalError( + "Invalid key type for rowid".to_string(), + )); + }; + + self.rowid = Some(*rowid as u64); + self.current = Some(record.clone()); + self.null_flag = false; + return Ok(CursorResult::Ok(true)); + } } } } @@ -781,19 +798,18 @@ mod tests { } mod test_index { - use std::{collections::BTreeMap, rc::Rc}; + use std::{collections::BTreeSet, rc::Rc}; use crate::{ ephemeral::{Ephemeral, EphemeralCursor}, schema::EphemeralIndex, - types::{CursorResult, LimboText, OwnedRecord, OwnedValue}, - LimboError, + types::{CursorResult, LimboText, OwnedRecord, OwnedValue, SeekKey, SeekOp}, }; #[test] fn test_next() { let mut table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; let val1 = OwnedRecord { @@ -802,17 +818,13 @@ mod tests { let val2 = OwnedRecord { values: vec![ - OwnedValue::Integer(44), OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Integer(44), ], }; - table - .rows - .insert((OwnedValue::Integer(42), 1), val1.clone()); - table - .rows - .insert((OwnedValue::Integer(44), 2), val2.clone()); + table.rows.insert(val1.clone()); + table.rows.insert(val2.clone()); let mut cursor = EphemeralCursor { source: Ephemeral::Index(table), @@ -831,7 +843,7 @@ mod tests { #[test] fn test_prev() { let mut table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; let val1 = OwnedRecord { @@ -840,17 +852,13 @@ mod tests { let val2 = OwnedRecord { values: vec![ - OwnedValue::Integer(44), OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Integer(44), ], }; - table - .rows - .insert((OwnedValue::Integer(42), 1), val1.clone()); - table - .rows - .insert((OwnedValue::Integer(44), 1), val2.clone()); + table.rows.insert(val1.clone()); + table.rows.insert(val2.clone()); let mut cursor = EphemeralCursor { source: Ephemeral::Index(table), rowid: None, @@ -865,14 +873,14 @@ mod tests { assert_eq!(cursor.current, Some(val1)); cursor.prev().unwrap(); // Should go out of bounds - assert!(cursor.current.is_none()); assert!(cursor.null_flag); + assert!(cursor.current.is_none()); } #[test] fn test_last() { let mut table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; let val1 = OwnedRecord { @@ -881,17 +889,13 @@ mod tests { let val2 = OwnedRecord { values: vec![ - OwnedValue::Integer(44), OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Integer(44), ], }; - table - .rows - .insert((OwnedValue::Integer(42), 1), val1.clone()); - table - .rows - .insert((OwnedValue::Integer(44), 2), val2.clone()); + table.rows.insert(val1.clone()); + table.rows.insert(val2.clone()); let mut cursor = EphemeralCursor { source: Ephemeral::Index(table), rowid: None, @@ -901,14 +905,14 @@ mod tests { cursor.last().unwrap(); // Move to the last row assert_eq!(cursor.current, Some(val2)); - assert_eq!(cursor.rowid, Some(2)); + assert_eq!(cursor.rowid, Some(44)); assert!(!cursor.null_flag); } #[test] fn test_last_empty_table() { let table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; @@ -928,7 +932,7 @@ mod tests { #[test] fn test_rewind() { let mut table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; @@ -938,16 +942,12 @@ mod tests { let val2 = OwnedRecord { values: vec![ - OwnedValue::Integer(44), OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Integer(44), ], }; - table - .rows - .insert((OwnedValue::Integer(42), 1), val1.clone()); - table - .rows - .insert((OwnedValue::Integer(44), 2), val2.clone()); + table.rows.insert(val1.clone()); + table.rows.insert(val2.clone()); let mut cursor = EphemeralCursor { source: Ephemeral::Index(table), @@ -958,14 +958,14 @@ mod tests { cursor.rewind().unwrap(); // Move to the first row assert_eq!(cursor.current, Some(val1)); - assert_eq!(cursor.rowid, Some(1)); + assert_eq!(cursor.rowid, Some(47)); assert!(!cursor.null_flag); } #[test] fn test_rewind_empty_table() { let table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; @@ -976,7 +976,7 @@ mod tests { null_flag: true, }; - cursor.rewind().unwrap(); // Calling rewind on an empty table + cursor.rewind().unwrap(); assert!(cursor.current.is_none()); assert!(cursor.null_flag); assert!(cursor.rowid.is_none()); @@ -985,7 +985,7 @@ mod tests { #[test] fn test_exists_key_found() { let mut table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; let val1 = OwnedRecord { @@ -993,17 +993,13 @@ mod tests { }; let val2 = OwnedRecord { values: vec![ - OwnedValue::Integer(44), OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Integer(44), ], }; - table - .rows - .insert((OwnedValue::Integer(42), 1), val1.clone()); - table - .rows - .insert((OwnedValue::Integer(44), 2), val2.clone()); + table.rows.insert(val1.clone()); + table.rows.insert(val2.clone()); let mut cursor = EphemeralCursor { source: Ephemeral::Index(table), @@ -1012,17 +1008,17 @@ mod tests { null_flag: true, }; - let result = cursor.exists(&OwnedValue::Integer(44)).unwrap(); + let result = cursor.exists(&OwnedValue::Integer(42)).unwrap(); assert_eq!(result, CursorResult::Ok(true)); - assert_eq!(cursor.rowid, Some(2)); - assert_eq!(cursor.current, Some(val2)); + assert_eq!(cursor.rowid, Some(47)); + assert_eq!(cursor.current, Some(val1)); assert!(!cursor.null_flag); } #[test] fn test_exists_key_not_found() { let mut table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; let val1 = OwnedRecord { @@ -1031,17 +1027,13 @@ mod tests { let val2 = OwnedRecord { values: vec![ - OwnedValue::Integer(44), OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Integer(44), ], }; - table - .rows - .insert((OwnedValue::Integer(42), 1), val1.clone()); - table - .rows - .insert((OwnedValue::Integer(44), 2), val2.clone()); + table.rows.insert(val1.clone()); + table.rows.insert(val2.clone()); let mut cursor = EphemeralCursor { source: Ephemeral::Index(table), @@ -1060,7 +1052,7 @@ mod tests { #[test] fn test_insert_new_row() { let table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; let record = OwnedRecord { @@ -1078,20 +1070,22 @@ mod tests { .insert(&OwnedValue::Integer(1), &record, false) .unwrap(); - if let Ephemeral::Index(table) = cursor.source { + let mut values = record.values.clone(); + values.push(OwnedValue::Integer(1)); + let result = OwnedRecord { values }; + + if let Ephemeral::Index(ref table) = cursor.source { assert_eq!(table.rows.len(), 1); - let expected_key = (OwnedValue::Integer(42), 1); - assert_eq!(table.rows.get(&expected_key), Some(&record)); assert_eq!(cursor.rowid, Some(1)); - assert_eq!(cursor.current, Some(record)); + assert_eq!(cursor.current, Some(result)); assert!(!cursor.null_flag); } } #[test] - fn test_insert_dont_overwrite_row() { + fn test_insert_overwrite_row() { let table = EphemeralIndex { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], }; @@ -1104,36 +1098,75 @@ mod tests { let key = OwnedValue::Integer(1); let record1 = OwnedRecord { - values: vec![OwnedValue::Text(LimboText::new(Rc::new( - "First".to_string(), - )))], + values: vec![ + OwnedValue::Text(LimboText::new(Rc::new("First".to_string()))), + OwnedValue::Integer(42), + ], }; - let id = OwnedValue::Text(LimboText::new(Rc::new("Second".to_string()))); let record2 = OwnedRecord { - values: vec![id.clone()], + values: vec![ + OwnedValue::Text(LimboText::new(Rc::new("Second".to_string()))), + OwnedValue::Integer(43), + ], }; cursor.insert(&key, &record1, false).unwrap(); cursor.insert(&key, &record2, true).unwrap(); - if let Ephemeral::Index(table) = cursor.source { - assert_eq!(table.rows.len(), 2); - let expected_key = (id, 1); - assert_eq!(table.rows.get(&expected_key), Some(&record2)); + let mut values = record2.values.clone(); + values.push(OwnedValue::Integer(1)); + let result = OwnedRecord { values }; + + if let Ephemeral::Index(ref table) = cursor.source { + assert_eq!(table.rows.len(), 1); assert_eq!(cursor.rowid, Some(1)); - assert_eq!(cursor.current, Some(record2)); + assert_eq!(cursor.current, Some(result)); assert!(!cursor.null_flag); } } #[test] - fn test_index_key_type_consistency() { - let index = EphemeralIndex { - rows: BTreeMap::new(), + fn test_do_seek_index_gt() { + let mut index = EphemeralIndex { + rows: BTreeSet::new(), columns: vec![], }; + let record1 = OwnedRecord { + values: vec![ + OwnedValue::Integer(1), + OwnedValue::Integer(10), + OwnedValue::Integer(100), + ], + }; + let record2 = OwnedRecord { + values: vec![ + OwnedValue::Integer(2), + OwnedValue::Integer(20), + OwnedValue::Integer(200), + ], + }; + let record3 = OwnedRecord { + values: vec![ + OwnedValue::Integer(2), + OwnedValue::Integer(25), + OwnedValue::Integer(300), + ], + }; + let record4 = OwnedRecord { + values: vec![ + OwnedValue::Integer(3), + OwnedValue::Integer(30), + OwnedValue::Integer(400), + ], + }; + + index.rows.insert(record1.clone()); + index.rows.insert(record2.clone()); + index.rows.insert(record3.clone()); + index.rows.insert(record4.clone()); + let mut cursor = EphemeralCursor { source: Ephemeral::Index(index), rowid: None, @@ -1141,25 +1174,18 @@ mod tests { null_flag: true, }; - // First insert: integer key - let record1 = OwnedRecord { - values: vec![OwnedValue::Integer(42)], + let key = OwnedRecord { + values: vec![OwnedValue::Integer(2), OwnedValue::Integer(20)], }; - let result1 = cursor.insert(&OwnedValue::Integer(1), &record1, false); - assert!(result1.is_ok(), "First insertion should succeed"); - // Second insert: text key (should fail) - let record2 = OwnedRecord { - values: vec![OwnedValue::Text(LimboText::new(Rc::new( - "Invalid".to_string(), - )))], - }; - let result2 = cursor.insert(&OwnedValue::Integer(2), &record2, false); + let search_key = SeekKey::IndexKey(&key); - assert!( - matches!(result2, Err(LimboError::InternalError(_))), - "Mismatched key type should result in an InternalError" - ); + let result = cursor.do_seek(search_key, SeekOp::GT).unwrap(); + + assert_eq!(result, CursorResult::Ok((Some(300), Some(record3.clone())))); + assert_eq!(cursor.rowid, Some(300)); + assert_eq!(cursor.current, Some(record3)); + assert!(!cursor.null_flag); } } } diff --git a/core/schema.rs b/core/schema.rs index d4d6f77d9..d35ca2aa3 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -10,7 +10,7 @@ use sqlite3_parser::{ ast::{Cmd, CreateTableBody, QualifiedName, ResultColumn, Stmt}, lexer::sql::Parser, }; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::rc::Rc; pub struct Schema { @@ -951,24 +951,20 @@ mod tests { } #[derive(Debug)] pub struct EphemeralIndex { - pub rows: BTreeMap, // rowid -> each index in the Vec is a column - pub columns: Vec, // columns + // Indexes rows has no data, all values from the record are used as key + pub rows: BTreeSet, + pub columns: Vec, } impl EphemeralIndex { pub fn new() -> Self { Self { - rows: BTreeMap::new(), + rows: BTreeSet::new(), columns: vec![], } } } -// What defines row uniqueness is the combination of the values of the first column with -// the rowid. In this way, we can have multiple rows pointing to the same rowid. -// https://www.sqlite.org/queryplanner.html -type IndexId = (OwnedValue, u64); - #[derive(Debug)] pub struct EphemeralTable { pub rows: BTreeMap>, // rowid -> each index in the Vec is a column From 21552c0e7cde66d536db7741b8a3d98a4a4b8c2c Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Sat, 8 Feb 2025 11:57:24 -0300 Subject: [PATCH 17/17] Adjust for updates in OwnedRecord and LimboText structs --- core/ephemeral.rs | 150 +++++++++++++++++++++------------------------- core/schema.rs | 5 +- core/vdbe/mod.rs | 13 +--- 3 files changed, 72 insertions(+), 96 deletions(-) diff --git a/core/ephemeral.rs b/core/ephemeral.rs index cda0bd8cb..f0d9346de 100644 --- a/core/ephemeral.rs +++ b/core/ephemeral.rs @@ -1,13 +1,13 @@ use crate::{ schema::{EphemeralIndex, EphemeralTable}, - types::{CursorResult, OwnedRecord, SeekKey, SeekOp}, + types::{CursorResult, Record, SeekKey, SeekOp}, LimboError, }; use crate::{types::OwnedValue, Result}; pub struct EphemeralCursor { source: Ephemeral, rowid: Option, - current: Option, + current: Option, null_flag: bool, } @@ -40,7 +40,7 @@ impl EphemeralCursor { &mut self, key: SeekKey<'_>, op: SeekOp, - ) -> Result, Option)>> { + ) -> Result, Option)>> { match &mut self.source { Ephemeral::Table(table) => { let SeekKey::TableRowId(rowid) = key else { @@ -63,7 +63,7 @@ impl EphemeralCursor { if let Some((id, values)) = entry { self.rowid = Some(id); - self.current = Some(OwnedRecord { values }); + self.current = Some(Record { values }); self.null_flag = false; return Ok(CursorResult::Ok((Some(id), self.current.clone()))); } @@ -76,7 +76,7 @@ impl EphemeralCursor { let search_key = index_key.clone(); // Exclude the last value for comparison - let trimmed_key = OwnedRecord { + let trimmed_key = Record { values: search_key.values[..search_key.values.len() - 1].to_vec(), }; @@ -116,7 +116,7 @@ impl EphemeralCursor { pub fn insert( &mut self, key: &OwnedValue, - record: &OwnedRecord, + record: &Record, moved_before: bool, ) -> Result> { match &mut self.source { @@ -172,7 +172,7 @@ impl EphemeralCursor { Ephemeral::Table(table) => { if let Some((&first_rowid, row_data)) = table.rows.iter().next() { self.rowid = Some(first_rowid); - self.current = Some(OwnedRecord { + self.current = Some(Record { values: row_data.clone(), }); self.null_flag = false; @@ -205,7 +205,7 @@ impl EphemeralCursor { Ephemeral::Table(table) => { if let Some((&last_rowid, row_data)) = table.rows.iter().next_back() { self.rowid = Some(last_rowid); - self.current = Some(OwnedRecord { + self.current = Some(Record { values: row_data.clone(), }); self.null_flag = false; @@ -239,7 +239,7 @@ impl EphemeralCursor { Ok(()) } - pub fn record(&self) -> Option<&OwnedRecord> { + pub fn record(&self) -> Option<&Record> { self.current.as_ref() } @@ -261,7 +261,7 @@ impl EphemeralCursor { if self.rowid.is_none() { if let Some((&first_rowid, row_data)) = table.rows.iter().next() { self.rowid = Some(first_rowid); - self.current = Some(OwnedRecord { + self.current = Some(Record { values: row_data.clone(), }); self.null_flag = false; @@ -272,7 +272,7 @@ impl EphemeralCursor { table.rows.range((current_rowid + 1)..).next() { self.rowid = Some(next_rowid); - self.current = Some(OwnedRecord { + self.current = Some(Record { values: row_data.clone(), }); self.null_flag = false; @@ -295,7 +295,7 @@ impl EphemeralCursor { } } else if let Some(current) = &self.current { let mut iter = index.rows.range(current..); - iter.next(); // ignore first result since we don't support Exclude in OwnedRecord. That would require the impl of RangeBound + iter.next(); // ignore first result since we don't support Exclude in Record. That would require the impl of RangeBound if let Some(row) = iter.next() { let OwnedValue::Integer(rowid) = row.values.last().unwrap() else { return Err(LimboError::InternalError( @@ -323,7 +323,7 @@ impl EphemeralCursor { if self.rowid.is_none() { if let Some((&first_rowid, row_data)) = table.rows.iter().next_back() { self.rowid = Some(first_rowid); - self.current = Some(OwnedRecord { + self.current = Some(Record { values: row_data.clone(), }); self.null_flag = false; @@ -334,7 +334,7 @@ impl EphemeralCursor { table.rows.range(..current_rowid).next_back() { self.rowid = Some(next_rowid); - self.current = Some(OwnedRecord { + self.current = Some(Record { values: row_data.clone(), }); self.null_flag = false; @@ -391,7 +391,7 @@ impl EphemeralCursor { if let Some(row) = table.rows.get(&(*key as u64)) { self.rowid = Some(*key as u64); - self.current = Some(OwnedRecord { + self.current = Some(Record { values: row.clone(), }); self.null_flag = false; @@ -401,7 +401,7 @@ impl EphemeralCursor { Ephemeral::Index(index) => { let search_key = match key { OwnedValue::Record(record) => record.clone(), - _ => OwnedRecord { + _ => Record { values: vec![key.clone()], }, }; @@ -441,7 +441,7 @@ mod tests { use crate::{ ephemeral::{Ephemeral, EphemeralCursor}, schema::EphemeralTable, - types::{CursorResult, LimboText, OwnedRecord, OwnedValue, SeekKey, SeekOp}, + types::{CursorResult, OwnedValue, Record, SeekKey, SeekOp, Text}, }; #[test] @@ -452,9 +452,7 @@ mod tests { columns: vec![], }; let val1 = vec![OwnedValue::Integer(42)]; - let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))]; + let val2 = vec![OwnedValue::Text(Text::new(Rc::new("Hello".to_string())))]; table.rows.insert(1, val1.clone()); table.rows.insert(2, val2.clone()); @@ -468,7 +466,7 @@ mod tests { cursor.next().unwrap(); // Move to the first row assert_eq!( cursor.current, - Some(OwnedRecord { + Some(Record { values: val1.clone() }) ); @@ -476,7 +474,7 @@ mod tests { cursor.next().unwrap(); // Move to the second row assert_eq!( cursor.current, - Some(OwnedRecord { + Some(Record { values: val2.clone() }) ); @@ -491,9 +489,7 @@ mod tests { }; let val1 = vec![OwnedValue::Integer(42)]; - let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))]; + let val2 = vec![OwnedValue::Text(Text::new(Rc::new("Hello".to_string())))]; table.rows.insert(1, val1.clone()); table.rows.insert(2, val2.clone()); @@ -507,7 +503,7 @@ mod tests { cursor.prev().unwrap(); // Should move to row 2 assert_eq!( cursor.current, - Some(OwnedRecord { + Some(Record { values: val2.clone() }) ); @@ -515,7 +511,7 @@ mod tests { cursor.prev().unwrap(); // Should move to row 1 assert_eq!( cursor.current, - Some(OwnedRecord { + Some(Record { values: val1.clone() }) ); @@ -534,9 +530,7 @@ mod tests { }; let val1 = vec![OwnedValue::Integer(42)]; - let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))]; + let val2 = vec![OwnedValue::Text(Text::new(Rc::new("Hello".to_string())))]; table.rows.insert(1, val1.clone()); table.rows.insert(2, val2.clone()); @@ -550,7 +544,7 @@ mod tests { cursor.last().unwrap(); // Move to the last row assert_eq!( cursor.current, - Some(OwnedRecord { + Some(Record { values: val2.clone() }) ); @@ -588,9 +582,7 @@ mod tests { }; let val1 = vec![OwnedValue::Integer(42)]; - let val2 = vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))]; + let val2 = vec![OwnedValue::Text(Text::new(Rc::new("Hello".to_string())))]; table.rows.insert(1, val1.clone()); table.rows.insert(2, val2.clone()); @@ -604,7 +596,7 @@ mod tests { cursor.rewind().unwrap(); // Move to the first row assert_eq!( cursor.current, - Some(OwnedRecord { + Some(Record { values: val1.clone() }) ); @@ -642,7 +634,7 @@ mod tests { }; let val1 = OwnedValue::Integer(42); - let val2 = OwnedValue::Text(LimboText::new(Rc::new("Hello".to_string()))); + let val2 = OwnedValue::Text(Text::new(Rc::new("Hello".to_string()))); table.rows.insert(1, vec![val1.clone()]); table.rows.insert(2, vec![val2.clone()]); @@ -658,7 +650,7 @@ mod tests { assert_eq!(cursor.rowid, Some(1)); assert_eq!( cursor.current, - Some(OwnedRecord { + Some(Record { values: vec![val1.clone()] }) ); @@ -674,7 +666,7 @@ mod tests { }; let val1 = OwnedValue::Integer(42); - let val2 = OwnedValue::Text(LimboText::new(Rc::new("Hello".to_string()))); + let val2 = OwnedValue::Text(Text::new(Rc::new("Hello".to_string()))); table.rows.insert(1, vec![val1.clone()]); table.rows.insert(2, vec![val2.clone()]); @@ -708,10 +700,8 @@ mod tests { }; let key = OwnedValue::Integer(1); - let record = OwnedRecord { - values: vec![OwnedValue::Text(LimboText::new(Rc::new( - "Hello".to_string(), - )))], + let record = Record { + values: vec![OwnedValue::Text(Text::new(Rc::new("Hello".to_string())))], }; cursor.insert(&key, &record, false).unwrap(); @@ -740,15 +730,11 @@ mod tests { }; let key = OwnedValue::Integer(1); - let record1 = OwnedRecord { - values: vec![OwnedValue::Text(LimboText::new(Rc::new( - "First".to_string(), - )))], + let record1 = Record { + values: vec![OwnedValue::Text(Text::new(Rc::new("First".to_string())))], }; - let record2 = OwnedRecord { - values: vec![OwnedValue::Text(LimboText::new(Rc::new( - "Second".to_string(), - )))], + let record2 = Record { + values: vec![OwnedValue::Text(Text::new(Rc::new("Second".to_string())))], }; cursor.insert(&key, &record1, false).unwrap(); @@ -787,7 +773,7 @@ mod tests { result, CursorResult::Ok(( Some(2), - Some(OwnedRecord { + Some(Record { values: vec![OwnedValue::Integer(20)] }) )) @@ -803,7 +789,7 @@ mod tests { use crate::{ ephemeral::{Ephemeral, EphemeralCursor}, schema::EphemeralIndex, - types::{CursorResult, LimboText, OwnedRecord, OwnedValue, SeekKey, SeekOp}, + types::{CursorResult, OwnedValue, Record, SeekKey, SeekOp, Text}, }; #[test] @@ -812,13 +798,13 @@ mod tests { rows: BTreeSet::new(), columns: vec![], }; - let val1 = OwnedRecord { + let val1 = Record { values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], }; - let val2 = OwnedRecord { + let val2 = Record { values: vec![ - OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Text(Text::new(std::rc::Rc::new("Hello".to_string()))), OwnedValue::Integer(44), ], }; @@ -846,13 +832,13 @@ mod tests { rows: BTreeSet::new(), columns: vec![], }; - let val1 = OwnedRecord { + let val1 = Record { values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], }; - let val2 = OwnedRecord { + let val2 = Record { values: vec![ - OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Text(Text::new(std::rc::Rc::new("Hello".to_string()))), OwnedValue::Integer(44), ], }; @@ -883,13 +869,13 @@ mod tests { rows: BTreeSet::new(), columns: vec![], }; - let val1 = OwnedRecord { + let val1 = Record { values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], }; - let val2 = OwnedRecord { + let val2 = Record { values: vec![ - OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Text(Text::new(std::rc::Rc::new("Hello".to_string()))), OwnedValue::Integer(44), ], }; @@ -936,13 +922,13 @@ mod tests { columns: vec![], }; - let val1 = OwnedRecord { + let val1 = Record { values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], }; - let val2 = OwnedRecord { + let val2 = Record { values: vec![ - OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Text(Text::new(std::rc::Rc::new("Hello".to_string()))), OwnedValue::Integer(44), ], }; @@ -988,12 +974,12 @@ mod tests { rows: BTreeSet::new(), columns: vec![], }; - let val1 = OwnedRecord { + let val1 = Record { values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], }; - let val2 = OwnedRecord { + let val2 = Record { values: vec![ - OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Text(Text::new(std::rc::Rc::new("Hello".to_string()))), OwnedValue::Integer(44), ], }; @@ -1021,13 +1007,13 @@ mod tests { rows: BTreeSet::new(), columns: vec![], }; - let val1 = OwnedRecord { + let val1 = Record { values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], }; - let val2 = OwnedRecord { + let val2 = Record { values: vec![ - OwnedValue::Text(LimboText::new(std::rc::Rc::new("Hello".to_string()))), + OwnedValue::Text(Text::new(std::rc::Rc::new("Hello".to_string()))), OwnedValue::Integer(44), ], }; @@ -1055,7 +1041,7 @@ mod tests { rows: BTreeSet::new(), columns: vec![], }; - let record = OwnedRecord { + let record = Record { values: vec![OwnedValue::Integer(42), OwnedValue::Integer(47)], }; @@ -1072,7 +1058,7 @@ mod tests { let mut values = record.values.clone(); values.push(OwnedValue::Integer(1)); - let result = OwnedRecord { values }; + let result = Record { values }; if let Ephemeral::Index(ref table) = cursor.source { assert_eq!(table.rows.len(), 1); @@ -1097,16 +1083,16 @@ mod tests { }; let key = OwnedValue::Integer(1); - let record1 = OwnedRecord { + let record1 = Record { values: vec![ - OwnedValue::Text(LimboText::new(Rc::new("First".to_string()))), + OwnedValue::Text(Text::new(Rc::new("First".to_string()))), OwnedValue::Integer(42), ], }; - let record2 = OwnedRecord { + let record2 = Record { values: vec![ - OwnedValue::Text(LimboText::new(Rc::new("Second".to_string()))), + OwnedValue::Text(Text::new(Rc::new("Second".to_string()))), OwnedValue::Integer(43), ], }; @@ -1116,7 +1102,7 @@ mod tests { let mut values = record2.values.clone(); values.push(OwnedValue::Integer(1)); - let result = OwnedRecord { values }; + let result = Record { values }; if let Ephemeral::Index(ref table) = cursor.source { assert_eq!(table.rows.len(), 1); @@ -1133,28 +1119,28 @@ mod tests { columns: vec![], }; - let record1 = OwnedRecord { + let record1 = Record { values: vec![ OwnedValue::Integer(1), OwnedValue::Integer(10), OwnedValue::Integer(100), ], }; - let record2 = OwnedRecord { + let record2 = Record { values: vec![ OwnedValue::Integer(2), OwnedValue::Integer(20), OwnedValue::Integer(200), ], }; - let record3 = OwnedRecord { + let record3 = Record { values: vec![ OwnedValue::Integer(2), OwnedValue::Integer(25), OwnedValue::Integer(300), ], }; - let record4 = OwnedRecord { + let record4 = Record { values: vec![ OwnedValue::Integer(3), OwnedValue::Integer(30), @@ -1174,7 +1160,7 @@ mod tests { null_flag: true, }; - let key = OwnedRecord { + let key = Record { values: vec![OwnedValue::Integer(2), OwnedValue::Integer(20)], }; diff --git a/core/schema.rs b/core/schema.rs index d35ca2aa3..be7ddcedf 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -1,5 +1,4 @@ -use crate::types::OwnedValue; -use crate::types::{OwnedRecord, OwnedValue}; +use crate::types::{OwnedValue, Record}; use crate::VirtualTable; use crate::{util::normalize_ident, Result}; use core::fmt; @@ -952,7 +951,7 @@ mod tests { #[derive(Debug)] pub struct EphemeralIndex { // Indexes rows has no data, all values from the record are used as key - pub rows: BTreeSet, + pub rows: BTreeSet, pub columns: Vec, } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 5bef97f68..820208a2f 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -328,7 +328,6 @@ impl Bitfield { } } -<<<<<<< HEAD pub struct VTabOpaqueCursor(*mut c_void); impl VTabOpaqueCursor { @@ -339,11 +338,11 @@ impl VTabOpaqueCursor { pub fn as_ptr(&self) -> *mut c_void { self.0 } -======= +} + pub enum GeneralCursor<'a> { BTree(&'a mut BTreeCursor), Ephemeral(&'a mut EphemeralCursor), ->>>>>>> fa7db68d (Adapt EphemeralCursor to recent changes and optimizations) } /// The program state describes the environment in which the program executes. @@ -1177,14 +1176,6 @@ impl Program { "Insn::Column on virtual table cursor, use Insn::VColumn instead" ); } - CursorType::Ephemeral(_) => { - let cursor = ephemeral_cursors.get_mut(cursor_id).unwrap(); - if let Some(record) = cursor.record() { - state.registers[*dest] = record.values[*column].clone(); - } else { - state.registers[*dest] = OwnedValue::Null - } - } } state.pc += 1;