diff --git a/bindings/go/rs_src/rows.rs b/bindings/go/rs_src/rows.rs index a1e6382d9..2de0bf8a2 100644 --- a/bindings/go/rs_src/rows.rs +++ b/bindings/go/rs_src/rows.rs @@ -2,7 +2,7 @@ use crate::{ types::{LimboValue, ResultCode}, LimboConn, }; -use limbo_core::{LimboError, Statement, StepResult}; +use limbo_core::{LimboError, OwnedValue, Statement, StepResult}; use std::ffi::{c_char, c_void}; pub struct LimboRows<'conn> { @@ -75,8 +75,8 @@ pub extern "C" fn rows_get_value(ctx: *mut c_void, col_idx: usize) -> *const c_v let ctx = LimboRows::from_ptr(ctx); if let Some(row) = ctx.stmt.row() { - if let Some(value) = row.get_values().get(col_idx) { - return LimboValue::from_value(value).to_ptr(); + if let Ok(value) = row.get::<&OwnedValue>(col_idx) { + return LimboValue::from_owned_value(value).to_ptr(); } } std::ptr::null() diff --git a/bindings/go/rs_src/types.rs b/bindings/go/rs_src/types.rs index c8508fea7..f58a4a0b0 100644 --- a/bindings/go/rs_src/types.rs +++ b/bindings/go/rs_src/types.rs @@ -1,5 +1,4 @@ use std::ffi::{c_char, c_void}; -use std::rc::Rc; #[allow(dead_code)] #[repr(C)] @@ -143,7 +142,7 @@ impl LimboValue { Box::into_raw(Box::new(self)) as *const c_void } - pub fn from_value(value: &limbo_core::OwnedValue) -> Self { + pub fn from_owned_value(value: &limbo_core::OwnedValue) -> Self { match value { limbo_core::OwnedValue::Integer(i) => { LimboValue::new(ValueType::Integer, ValueUnion::from_int(*i)) @@ -155,7 +154,7 @@ impl LimboValue { LimboValue::new(ValueType::Text, ValueUnion::from_str(s.as_str())) } limbo_core::OwnedValue::Blob(b) => { - LimboValue::new(ValueType::Blob, ValueUnion::from_bytes(b)) + LimboValue::new(ValueType::Blob, ValueUnion::from_bytes(b.as_slice())) } limbo_core::OwnedValue::Null => { LimboValue::new(ValueType::Null, ValueUnion::from_null()) @@ -198,7 +197,7 @@ impl LimboValue { return limbo_core::OwnedValue::Null; } let bytes = self.value.to_bytes(); - limbo_core::OwnedValue::Blob(Rc::new(bytes.to_vec())) + limbo_core::OwnedValue::Blob(bytes.to_vec()) } ValueType::Null => limbo_core::OwnedValue::Null, } diff --git a/bindings/java/rs_src/limbo_statement.rs b/bindings/java/rs_src/limbo_statement.rs index 0a6cf8c3b..b28ff55b1 100644 --- a/bindings/java/rs_src/limbo_statement.rs +++ b/bindings/java/rs_src/limbo_statement.rs @@ -7,7 +7,6 @@ use jni::sys::{jdouble, jint, jlong}; use jni::JNIEnv; use limbo_core::{OwnedValue, Statement, StepResult}; use std::num::NonZero; -use std::rc::Rc; pub const STEP_RESULT_ID_ROW: i32 = 10; #[allow(dead_code)] @@ -105,7 +104,7 @@ fn row_to_obj_array<'local>( ) -> Result> { let obj_array = env.new_object_array(row.len() as i32, "java/lang/Object", JObject::null())?; - for (i, value) in row.get_values().iter().enumerate() { + for (i, value) in row.get_values().enumerate() { let obj = match value { limbo_core::OwnedValue::Null => JObject::null(), limbo_core::OwnedValue::Integer(i) => { @@ -115,7 +114,7 @@ fn row_to_obj_array<'local>( env.new_object("java/lang/Double", "(D)V", &[JValue::Double(*f)])? } limbo_core::OwnedValue::Text(s) => env.new_string(s.as_str())?.into(), - limbo_core::OwnedValue::Blob(b) => env.byte_array_from_slice(&b)?.into(), + limbo_core::OwnedValue::Blob(b) => env.byte_array_from_slice(&b.as_slice())?.into(), }; if let Err(e) = env.set_object_array_element(&obj_array, i as i32, obj) { eprintln!("Error on parsing row: {:?}", e); @@ -264,7 +263,7 @@ pub extern "system" fn Java_tech_turso_core_LimboStatement_bindBlob<'local>( stmt.stmt.bind_at( NonZero::new(position as usize).unwrap(), - OwnedValue::Blob(Rc::new(blob)), + OwnedValue::Blob(blob), ); SQLITE_OK } diff --git a/bindings/javascript/src/lib.rs b/bindings/javascript/src/lib.rs index 51ef0db9d..614b17677 100644 --- a/bindings/javascript/src/lib.rs +++ b/bindings/javascript/src/lib.rs @@ -76,7 +76,7 @@ impl Statement { Ok(limbo_core::StepResult::Row) => { let row = stmt.row().unwrap(); let mut obj = env.create_object()?; - for (idx, value) in row.get_values().iter().enumerate() { + for (idx, value) in row.get_values().enumerate() { let key = stmt.get_column_name(idx); let js_value = to_js_value(&env, value); obj.set_named_property(&key, js_value)?; @@ -99,7 +99,7 @@ fn to_js_value(env: &napi::Env, value: &limbo_core::OwnedValue) -> JsUnknown { limbo_core::OwnedValue::Float(f) => env.create_double(*f).unwrap().into_unknown(), limbo_core::OwnedValue::Text(s) => env.create_string(s.as_str()).unwrap().into_unknown(), limbo_core::OwnedValue::Blob(b) => { - env.create_buffer_copy(b.as_ref()).unwrap().into_unknown() + env.create_buffer_copy(b.as_slice()).unwrap().into_unknown() } } } diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 6167e069e..851136b50 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -352,7 +352,7 @@ fn py_to_owned_value(obj: &Bound) -> Result { } else if let Ok(string) = obj.extract::() { return Ok(OwnedValue::Text(Text::from_str(string))); } else if let Ok(bytes) = obj.downcast::() { - return Ok(OwnedValue::Blob(Rc::new(bytes.as_bytes().to_vec()))); + return Ok(OwnedValue::Blob(bytes.as_bytes().to_vec())); } else { return Err(PyErr::new::(format!( "Unsupported Python type: {}", diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index aefc9ab8f..fe653fdb5 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -212,7 +212,7 @@ impl Rows { Ok(limbo_core::StepResult::Row) => { let row = stmt.row().unwrap(); Ok(Some(Row { - values: row.get_values().to_vec(), + values: row.get_values().map(|v| v.to_owned()).collect(), })) } _ => Ok(None), diff --git a/cli/app.rs b/cli/app.rs index 1e12ae42f..aa6556869 100644 --- a/cli/app.rs +++ b/cli/app.rs @@ -319,7 +319,6 @@ impl<'a> Limbo<'a> { |row: &limbo_core::Row| -> Result<(), LimboError> { let values = row .get_values() - .iter() .zip(value_types.iter()) .map(|(value, value_type)| { // If the type affinity is TEXT, replace each single @@ -711,7 +710,7 @@ impl<'a> Limbo<'a> { match rows.step() { Ok(StepResult::Row) => { let row = rows.row().unwrap(); - for (i, value) in row.get_values().iter().enumerate() { + for (i, value) in row.get_values().enumerate() { if i > 0 { let _ = self.writer.write(b"|"); } @@ -767,7 +766,7 @@ impl<'a> Limbo<'a> { let record = rows.row().unwrap(); let mut row = Row::new(); row.max_height(1); - for (idx, value) in record.get_values().iter().enumerate() { + for (idx, value) in record.get_values().enumerate() { let (content, alignment) = match value { OwnedValue::Null => { (self.opts.null_value.clone(), CellAlignment::Left) @@ -849,7 +848,7 @@ impl<'a> Limbo<'a> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - if let Some(OwnedValue::Text(schema)) = row.get_values().first() { + if let Ok(OwnedValue::Text(schema)) = row.get::<&OwnedValue>(0) { let _ = self.write_fmt(format_args!("{};", schema.as_str())); found = true; } @@ -907,7 +906,7 @@ impl<'a> Limbo<'a> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - if let Some(OwnedValue::Text(table)) = row.get_values().first() { + if let Ok(OwnedValue::Text(table)) = row.get::<&OwnedValue>(0) { tables.push_str(table.as_str()); tables.push(' '); } diff --git a/core/json/json_operations.rs b/core/json/json_operations.rs index 3c83c3ba3..ea0ba9560 100644 --- a/core/json/json_operations.rs +++ b/core/json/json_operations.rs @@ -1,4 +1,4 @@ -use std::{collections::VecDeque, rc::Rc}; +use std::collections::VecDeque; use crate::{types::OwnedValue, vdbe::Register}; @@ -185,7 +185,7 @@ pub fn jsonb_remove(args: &[Register], json_cache: &JsonCacheCell) -> crate::Res } } - Ok(OwnedValue::Blob(Rc::new(json.data()))) + Ok(OwnedValue::Blob(json.data())) } pub fn json_replace(args: &[Register], json_cache: &JsonCacheCell) -> crate::Result { @@ -283,8 +283,6 @@ pub fn jsonb_insert(args: &[Register], json_cache: &JsonCacheCell) -> crate::Res #[cfg(test)] mod tests { - use std::rc::Rc; - use crate::types::Text; use super::*; @@ -554,7 +552,7 @@ mod tests { #[test] #[should_panic(expected = "blob is not supported!")] fn test_blob_not_supported() { - let target = OwnedValue::Blob(Rc::new(vec![1, 2, 3])); + let target = OwnedValue::Blob(vec![1, 2, 3]); let patch = create_text("{}"); json_patch(&target, &patch).unwrap(); } diff --git a/core/json/mod.rs b/core/json/mod.rs index 039440d1d..aed8c1cd2 100644 --- a/core/json/mod.rs +++ b/core/json/mod.rs @@ -22,7 +22,6 @@ use jsonb::{ElementType, Jsonb, JsonbHeader, PathOperationMode, SearchOperation, use ser::to_string_pretty; use serde::{Deserialize, Serialize}; use std::borrow::Cow; -use std::rc::Rc; use std::str::FromStr; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] @@ -73,7 +72,7 @@ pub fn get_json(json_value: &OwnedValue, indent: Option<&str>) -> crate::Result< let jsonbin = Jsonb::new(b.len(), Some(b)); jsonbin.is_valid()?; Ok(OwnedValue::Text(Text { - value: Rc::new(jsonbin.to_string()?.into_bytes()), + value: jsonbin.to_string()?.into_bytes(), subtype: TextSubtype::Json, })) } @@ -95,7 +94,7 @@ pub fn jsonb(json_value: &OwnedValue, cache: &JsonCacheCell) -> crate::Result Ok(OwnedValue::Blob(Rc::new(jsonbin.data()))), + Ok(jsonbin) => Ok(OwnedValue::Blob(jsonbin.data())), Err(_) => { bail_parse_error!("malformed JSON") } @@ -405,7 +404,7 @@ fn json_string_to_db_type( ) -> crate::Result { let mut json_string = json.to_string()?; if matches!(flag, OutputVariant::Binary) { - return Ok(OwnedValue::Blob(Rc::new(json.data()))); + return Ok(OwnedValue::Blob(json.data())); } match element_type { ElementType::ARRAY | ElementType::OBJECT => Ok(OwnedValue::Text(Text::json(json_string))), @@ -414,12 +413,12 @@ fn json_string_to_db_type( json_string.remove(json_string.len() - 1); json_string.remove(0); Ok(OwnedValue::Text(Text { - value: Rc::new(json_string.into_bytes()), + value: json_string.into_bytes(), subtype: TextSubtype::Json, })) } else { Ok(OwnedValue::Text(Text { - value: Rc::new(json_string.into_bytes()), + value: json_string.into_bytes(), subtype: TextSubtype::Text, })) } @@ -664,8 +663,6 @@ pub fn json_quote(value: &OwnedValue) -> crate::Result { #[cfg(test)] mod tests { - use std::rc::Rc; - use super::*; use crate::types::OwnedValue; @@ -764,7 +761,7 @@ mod tests { #[test] fn test_get_json_blob_valid_jsonb() { let binary_json = vec![124, 55, 104, 101, 121, 39, 121, 111]; - let input = OwnedValue::Blob(Rc::new(binary_json)); + let input = OwnedValue::Blob(binary_json); let result = get_json(&input, None).unwrap(); if let OwnedValue::Text(result_str) = result { assert!(result_str.as_str().contains(r#"{"hey":"yo"}"#)); @@ -777,7 +774,7 @@ mod tests { #[test] fn test_get_json_blob_invalid_jsonb() { let binary_json: Vec = vec![0xA2, 0x62, 0x6B, 0x31, 0x62, 0x76]; // Incomplete binary JSON - let input = OwnedValue::Blob(Rc::new(binary_json)); + let input = OwnedValue::Blob(binary_json); let result = get_json(&input, None); println!("{:?}", result); match result { @@ -832,7 +829,7 @@ mod tests { #[test] fn test_json_array_blob_invalid() { - let blob = Register::OwnedValue(OwnedValue::Blob(Rc::new("1".as_bytes().to_vec()))); + let blob = Register::OwnedValue(OwnedValue::Blob("1".as_bytes().to_vec())); let input = vec![blob]; diff --git a/core/lib.rs b/core/lib.rs index c9f0342db..f1a1b37a0 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -63,6 +63,7 @@ use storage::{ }; use translate::select::prepare_select_plan; pub use types::OwnedValue; +pub use types::RefValue; use util::{columns_from_create_table_body, parse_schema_rows}; use vdbe::{builder::QueryMode, VTabOpaqueCursor}; @@ -596,7 +597,7 @@ impl Statement { } } -pub type Row = types::Record; +pub type Row = vdbe::Row; pub type StepResult = vdbe::StepResult; diff --git a/core/pseudo.rs b/core/pseudo.rs index bcc6c91f0..c4bb1ee40 100644 --- a/core/pseudo.rs +++ b/core/pseudo.rs @@ -1,7 +1,7 @@ -use crate::types::Record; +use crate::types::ImmutableRecord; pub struct PseudoCursor { - current: Option, + current: Option, } impl PseudoCursor { @@ -9,11 +9,11 @@ impl PseudoCursor { Self { current: None } } - pub fn record(&self) -> Option<&Record> { + pub fn record(&self) -> Option<&ImmutableRecord> { self.current.as_ref() } - pub fn insert(&mut self, record: Record) { + pub fn insert(&mut self, record: ImmutableRecord) { self.current = Some(record); } } diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 393079029..42138ff9b 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -7,8 +7,7 @@ use crate::storage::sqlite3_ondisk::{ use crate::MvCursor; use crate::types::{ - compare_immutable_to_record, compare_record_to_immutable, CursorResult, ImmutableRecord, - OwnedValue, Record, RefValue, SeekKey, SeekOp, + compare_immutable, CursorResult, ImmutableRecord, OwnedValue, RefValue, SeekKey, SeekOp, }; use crate::{return_corrupt, LimboError, Result}; @@ -246,7 +245,6 @@ pub struct BTreeCursor { root_page: usize, /// Rowid and record are stored before being consumed. rowid: Cell>, - record: RefCell>, null_flag: bool, /// Index internal pages are consumed on the way up, so we store going upwards flag in case /// we just moved to a parent page and the parent page is an internal index page which requires @@ -260,6 +258,9 @@ pub struct BTreeCursor { /// Page stack used to traverse the btree. /// Each cursor has a stack because each cursor traverses the btree independently. stack: PageStack, + /// Reusable immutable record, used to allow better allocation strategy. + reusable_immutable_record: RefCell>, + empty_record: Cell, } /// Stack of pages representing the tree traversal order. @@ -297,7 +298,6 @@ impl BTreeCursor { pager, root_page, rowid: Cell::new(None), - record: RefCell::new(None), null_flag: false, going_upwards: false, state: CursorState::None, @@ -307,6 +307,8 @@ impl BTreeCursor { cell_indices: RefCell::new([0; BTCURSOR_MAX_DEPTH + 1]), stack: RefCell::new([const { None }; BTCURSOR_MAX_DEPTH + 1]), }, + reusable_immutable_record: RefCell::new(None), + empty_record: Cell::new(true), } } @@ -326,7 +328,7 @@ impl BTreeCursor { /// Move the cursor to the previous record and return it. /// Used in backwards iteration. - fn get_prev_record(&mut self) -> Result, Option)>> { + fn get_prev_record(&mut self) -> Result>> { loop { let page = self.stack.top(); let cell_idx = self.stack.current_cell_index(); @@ -343,7 +345,7 @@ impl BTreeCursor { self.stack.pop(); } else { // moved to begin of btree - return Ok(CursorResult::Ok((None, None))); + return Ok(CursorResult::Ok(None)); } } // continue to next loop to get record from the new page @@ -395,13 +397,16 @@ impl BTreeCursor { first_overflow_page, payload_size, }) => { - let record = if let Some(next_page) = first_overflow_page { + if let Some(next_page) = first_overflow_page { return_if_io!(self.process_overflow_read(_payload, next_page, payload_size)) } else { - crate::storage::sqlite3_ondisk::read_record(_payload)? + crate::storage::sqlite3_ondisk::read_record( + _payload, + self.get_immutable_record_or_create().as_mut().unwrap(), + )? }; self.stack.retreat(); - return Ok(CursorResult::Ok((Some(_rowid), Some(record)))); + return Ok(CursorResult::Ok(Some(_rowid))); } BTreeCell::IndexInteriorCell(_) => todo!(), BTreeCell::IndexLeafCell(_) => todo!(), @@ -416,7 +421,7 @@ impl BTreeCursor { payload: &'static [u8], start_next_page: u32, payload_size: u64, - ) -> Result> { + ) -> Result> { let res = match &mut self.state { CursorState::None => { tracing::debug!("start reading overflow page payload_size={}", payload_size); @@ -452,8 +457,9 @@ impl BTreeCursor { *remaining_to_read == 0 && next == 0, "we can't have more pages to read while also have read everything" ); - let record = crate::storage::sqlite3_ondisk::read_record(&payload)?; - CursorResult::Ok(record) + let mut payload_swap = Vec::new(); + std::mem::swap(payload, &mut payload_swap); + CursorResult::Ok(payload_swap) } else { let new_page = self.pager.read_page(next as usize)?; *page = new_page; @@ -463,10 +469,20 @@ impl BTreeCursor { } _ => unreachable!(), }; - if matches!(res, CursorResult::Ok(..)) { - self.state = CursorState::None; + match res { + CursorResult::Ok(payload) => { + { + let mut reuse_immutable = self.get_immutable_record_or_create(); + crate::storage::sqlite3_ondisk::read_record( + &payload, + reuse_immutable.as_mut().unwrap(), + )?; + } + self.state = CursorState::None; + Ok(CursorResult::Ok(())) + } + CursorResult::IO => Ok(CursorResult::IO), } - Ok(res) } /// Move the cursor to the next record and return it. @@ -474,18 +490,21 @@ impl BTreeCursor { fn get_next_record( &mut self, predicate: Option<(SeekKey<'_>, SeekOp)>, - ) -> Result, Option)>> { + ) -> Result>> { if let Some(mv_cursor) = &self.mv_cursor { let mut mv_cursor = mv_cursor.borrow_mut(); let rowid = mv_cursor.current_row_id(); match rowid { Some(rowid) => { let record = mv_cursor.current_row().unwrap().unwrap(); - let record = crate::storage::sqlite3_ondisk::read_record(&record.data)?; + crate::storage::sqlite3_ondisk::read_record( + &record.data, + self.get_immutable_record_or_create().as_mut().unwrap(), + )?; mv_cursor.forward(); - return Ok(CursorResult::Ok((Some(rowid.row_id), Some(record)))); + return Ok(CursorResult::Ok(Some(rowid.row_id))); } - None => return Ok(CursorResult::Ok((None, None))), + None => return Ok(CursorResult::Ok(None)), } } loop { @@ -519,7 +538,7 @@ impl BTreeCursor { self.stack.pop(); continue; } else { - return Ok(CursorResult::Ok((None, None))); + return Ok(CursorResult::Ok(None)); } } } @@ -534,7 +553,7 @@ impl BTreeCursor { self.stack.pop(); continue; } else { - return Ok(CursorResult::Ok((None, None))); + return Ok(CursorResult::Ok(None)); } } assert!(cell_idx < contents.cell_count()); @@ -563,17 +582,20 @@ impl BTreeCursor { payload_size, }) => { assert!(predicate.is_none()); - let record = if let Some(next_page) = first_overflow_page { + if let Some(next_page) = first_overflow_page { return_if_io!(self.process_overflow_read( _payload, *next_page, *payload_size )) } else { - crate::storage::sqlite3_ondisk::read_record(_payload)? + crate::storage::sqlite3_ondisk::read_record( + _payload, + self.get_immutable_record_or_create().as_mut().unwrap(), + )? }; self.stack.advance(); - return Ok(CursorResult::Ok((Some(*_rowid), Some(record)))); + return Ok(CursorResult::Ok(Some(*_rowid))); } BTreeCell::IndexInteriorCell(IndexInteriorCell { payload, @@ -586,43 +608,50 @@ impl BTreeCursor { self.stack.push(mem_page); continue; } - let record = if let Some(next_page) = first_overflow_page { + if let Some(next_page) = first_overflow_page { return_if_io!(self.process_overflow_read( payload, *next_page, *payload_size )) } else { - crate::storage::sqlite3_ondisk::read_record(payload)? + crate::storage::sqlite3_ondisk::read_record( + payload, + self.get_immutable_record_or_create().as_mut().unwrap(), + )? }; self.going_upwards = false; self.stack.advance(); if predicate.is_none() { - let rowid = match record.last_value() { + let rowid = match self.get_immutable_record().as_ref().unwrap().last_value() + { Some(RefValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; - return Ok(CursorResult::Ok((Some(rowid), Some(record)))); + return Ok(CursorResult::Ok(Some(rowid))); } let (key, op) = predicate.as_ref().unwrap(); let SeekKey::IndexKey(index_key) = key else { unreachable!("index seek key should be a record"); }; - let order = - compare_immutable_to_record(&record.get_values(), &index_key.get_values()); + let order = compare_immutable( + &self.get_immutable_record().as_ref().unwrap().get_values(), + index_key.get_values(), + ); let found = match op { SeekOp::GT => order.is_gt(), SeekOp::GE => order.is_ge(), SeekOp::EQ => order.is_eq(), }; if found { - let rowid = match record.last_value() { + let rowid = match self.get_immutable_record().as_ref().unwrap().last_value() + { Some(RefValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; - return Ok(CursorResult::Ok((Some(rowid), Some(record)))); + return Ok(CursorResult::Ok(Some(rowid))); } else { continue; } @@ -632,41 +661,48 @@ impl BTreeCursor { first_overflow_page, payload_size, }) => { - let record = if let Some(next_page) = first_overflow_page { + if let Some(next_page) = first_overflow_page { return_if_io!(self.process_overflow_read( payload, *next_page, *payload_size )) } else { - crate::storage::sqlite3_ondisk::read_record(payload)? + crate::storage::sqlite3_ondisk::read_record( + payload, + self.get_immutable_record_or_create().as_mut().unwrap(), + )? }; self.stack.advance(); if predicate.is_none() { - let rowid = match record.last_value() { + let rowid = match self.get_immutable_record().as_ref().unwrap().last_value() + { Some(RefValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; - return Ok(CursorResult::Ok((Some(rowid), Some(record)))); + return Ok(CursorResult::Ok(Some(rowid))); } let (key, op) = predicate.as_ref().unwrap(); let SeekKey::IndexKey(index_key) = key else { unreachable!("index seek key should be a record"); }; - let order = - compare_immutable_to_record(&record.get_values(), &index_key.get_values()); + let order = compare_immutable( + &self.get_immutable_record().as_ref().unwrap().get_values(), + index_key.get_values(), + ); let found = match op { SeekOp::GT => order.is_lt(), SeekOp::GE => order.is_le(), SeekOp::EQ => order.is_le(), }; if found { - let rowid = match record.last_value() { + let rowid = match self.get_immutable_record().as_ref().unwrap().last_value() + { Some(RefValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; - return Ok(CursorResult::Ok((Some(rowid), Some(record)))); + return Ok(CursorResult::Ok(Some(rowid))); } else { continue; } @@ -679,11 +715,7 @@ impl BTreeCursor { /// This may be used to seek to a specific record in a point query (e.g. SELECT * FROM table WHERE col = 10) /// or e.g. find the first record greater than the seek key in a range query (e.g. SELECT * FROM table WHERE col > 10). /// We don't include the rowid in the comparison and that's why the last value from the record is not included. - fn do_seek( - &mut self, - key: SeekKey<'_>, - op: SeekOp, - ) -> Result, Option)>> { + fn do_seek(&mut self, key: SeekKey<'_>, op: SeekOp) -> Result>> { return_if_io!(self.move_to(key.clone(), op.clone())); { @@ -721,17 +753,20 @@ impl BTreeCursor { SeekOp::EQ => *cell_rowid == rowid_key, }; if found { - let record = if let Some(next_page) = first_overflow_page { + if let Some(next_page) = first_overflow_page { return_if_io!(self.process_overflow_read( payload, *next_page, *payload_size )) } else { - crate::storage::sqlite3_ondisk::read_record(payload)? + crate::storage::sqlite3_ondisk::read_record( + payload, + self.get_immutable_record_or_create().as_mut().unwrap(), + )? }; self.stack.advance(); - return Ok(CursorResult::Ok((Some(*cell_rowid), Some(record)))); + return Ok(CursorResult::Ok(Some(*cell_rowid))); } else { self.stack.advance(); } @@ -744,16 +779,21 @@ impl BTreeCursor { let SeekKey::IndexKey(index_key) = key else { unreachable!("index seek key should be a record"); }; - let record = if let Some(next_page) = first_overflow_page { + if let Some(next_page) = first_overflow_page { return_if_io!(self.process_overflow_read( payload, *next_page, *payload_size )) } else { - crate::storage::sqlite3_ondisk::read_record(payload)? + crate::storage::sqlite3_ondisk::read_record( + payload, + self.get_immutable_record_or_create().as_mut().unwrap(), + )? }; - let order = compare_immutable_to_record( + let record = self.get_immutable_record(); + let record = record.as_ref().unwrap(); + let order = compare_immutable( &record.get_values().as_slice()[..record.len() - 1], &index_key.get_values().as_slice()[..], ); @@ -768,7 +808,7 @@ impl BTreeCursor { Some(RefValue::Integer(rowid)) => *rowid as u64, _ => unreachable!("index cells should have an integer rowid"), }; - return Ok(CursorResult::Ok((Some(rowid), Some(record)))); + return Ok(CursorResult::Ok(Some(rowid))); } } cell_type => { @@ -798,7 +838,7 @@ impl BTreeCursor { return self.get_next_record(Some((key, op))); } - Ok(CursorResult::Ok((None, None))) + Ok(CursorResult::Ok(None)) } /// Move the cursor to the root page of the btree. @@ -930,18 +970,21 @@ impl BTreeCursor { let SeekKey::IndexKey(index_key) = key else { unreachable!("index seek key should be a record"); }; - let record = if let Some(next_page) = first_overflow_page { + if let Some(next_page) = first_overflow_page { return_if_io!(self.process_overflow_read( payload, *next_page, *payload_size )) } else { - crate::storage::sqlite3_ondisk::read_record(payload)? + crate::storage::sqlite3_ondisk::read_record( + payload, + self.get_immutable_record_or_create().as_mut().unwrap(), + )? }; - let order = compare_record_to_immutable( - &index_key.get_values(), - &record.get_values(), + let order = compare_immutable( + index_key.get_values(), + self.get_immutable_record().as_ref().unwrap().get_values(), ); let target_leaf_page_is_in_the_left_subtree = match cmp { SeekOp::GT => order.is_lt(), @@ -984,7 +1027,11 @@ impl BTreeCursor { /// Insert a record into the btree. /// If the insert operation overflows the page, it will be split and the btree will be balanced. - fn insert_into_page(&mut self, key: &OwnedValue, record: &Record) -> Result> { + fn insert_into_page( + &mut self, + key: &OwnedValue, + record: &ImmutableRecord, + ) -> Result> { if let CursorState::None = &self.state { self.state = CursorState::Write(WriteInfo::new()); } @@ -1854,19 +1901,18 @@ impl BTreeCursor { pub fn seek_to_last(&mut self) -> Result> { return_if_io!(self.move_to_rightmost()); - let (rowid, record) = return_if_io!(self.get_next_record(None)); + let rowid = return_if_io!(self.get_next_record(None)); if rowid.is_none() { let is_empty = return_if_io!(self.is_empty_table()); assert!(is_empty); return Ok(CursorResult::Ok(())); } self.rowid.replace(rowid); - self.record.replace(record); Ok(CursorResult::Ok(())) } pub fn is_empty(&self) -> bool { - self.record.borrow().is_none() + self.empty_record.get() } pub fn root_page(&self) -> usize { @@ -1875,15 +1921,15 @@ impl BTreeCursor { pub fn rewind(&mut self) -> Result> { if self.mv_cursor.is_some() { - let (rowid, record) = return_if_io!(self.get_next_record(None)); + let rowid = return_if_io!(self.get_next_record(None)); self.rowid.replace(rowid); - self.record.replace(record); + self.empty_record.replace(rowid.is_none()); } else { self.move_to_root(); - let (rowid, record) = return_if_io!(self.get_next_record(None)); + let rowid = return_if_io!(self.get_next_record(None)); self.rowid.replace(rowid); - self.record.replace(record); + self.empty_record.replace(rowid.is_none()); } Ok(CursorResult::Ok(())) } @@ -1897,18 +1943,18 @@ impl BTreeCursor { } pub fn next(&mut self) -> Result> { - let (rowid, record) = return_if_io!(self.get_next_record(None)); + let rowid = return_if_io!(self.get_next_record(None)); self.rowid.replace(rowid); - self.record.replace(record); + self.empty_record.replace(rowid.is_none()); Ok(CursorResult::Ok(())) } pub fn prev(&mut self) -> Result> { assert!(self.mv_cursor.is_none()); match self.get_prev_record()? { - CursorResult::Ok((rowid, record)) => { + CursorResult::Ok(rowid) => { self.rowid.replace(rowid); - self.record.replace(record); + self.empty_record.replace(rowid.is_none()); Ok(CursorResult::Ok(())) } CursorResult::IO => Ok(CursorResult::IO), @@ -1930,20 +1976,20 @@ impl BTreeCursor { pub fn seek(&mut self, key: SeekKey<'_>, op: SeekOp) -> Result> { assert!(self.mv_cursor.is_none()); - let (rowid, record) = return_if_io!(self.do_seek(key, op)); + let rowid = return_if_io!(self.do_seek(key, op)); self.rowid.replace(rowid); - self.record.replace(record); + self.empty_record.replace(rowid.is_none()); Ok(CursorResult::Ok(rowid.is_some())) } pub fn record(&self) -> Ref> { - self.record.borrow() + self.reusable_immutable_record.borrow() } pub fn insert( &mut self, key: &OwnedValue, - record: &Record, + record: &ImmutableRecord, moved_before: bool, /* Indicate whether it's necessary to traverse to find the leaf page */ ) -> Result> { let int_key = match key { @@ -1954,8 +2000,7 @@ impl BTreeCursor { Some(mv_cursor) => { let row_id = crate::mvcc::database::RowID::new(self.table_id() as u64, *int_key as u64); - let mut record_buf = Vec::new(); - record.serialize(&mut record_buf); + let record_buf = record.get_payload().to_vec(); let row = crate::mvcc::database::Row::new(row_id, record_buf); mv_cursor.borrow_mut().insert(row).unwrap(); } @@ -2594,7 +2639,7 @@ impl BTreeCursor { &mut self, page_ref: PageRef, cell_idx: usize, - record: &Record, + record: &ImmutableRecord, ) -> Result> { // build the new payload let page_type = page_ref.get().contents.as_ref().unwrap().page_type(); @@ -2691,6 +2736,18 @@ impl BTreeCursor { } Ok(CursorResult::Ok(())) } + + fn get_immutable_record_or_create(&self) -> std::cell::RefMut<'_, Option> { + if self.reusable_immutable_record.borrow().is_none() { + let record = ImmutableRecord::new(4096, 10); + self.reusable_immutable_record.replace(Some(record)); + } + self.reusable_immutable_record.borrow_mut() + } + + fn get_immutable_record(&self) -> std::cell::RefMut<'_, Option> { + self.reusable_immutable_record.borrow_mut() + } } impl PageStack { @@ -3354,7 +3411,7 @@ fn fill_cell_payload( page_type: PageType, int_key: Option, cell_payload: &mut Vec, - record: &Record, + record: &ImmutableRecord, usable_space: u16, pager: Rc, ) { @@ -3363,8 +3420,7 @@ fn fill_cell_payload( PageType::TableLeaf | PageType::IndexLeaf )); // TODO: make record raw from start, having to serialize is not good - let mut record_buf = Vec::new(); - record.serialize(&mut record_buf); + let record_buf = record.get_payload().to_vec(); // fill in header if matches!(page_type, PageType::TableLeaf) { @@ -3537,6 +3593,7 @@ mod tests { use crate::storage::sqlite3_ondisk; use crate::storage::sqlite3_ondisk::DatabaseHeader; use crate::types::Text; + use crate::vdbe::Register; use crate::Connection; use crate::{BufferPool, DatabaseStorage, WalFile, WalFileShared, WriteCompletion}; use std::cell::RefCell; @@ -3558,7 +3615,7 @@ mod tests { pager::PageRef, sqlite3_ondisk::{BTreeCell, PageContent, PageType}, }, - types::{OwnedValue, Record}, + types::OwnedValue, Database, Page, Pager, PlatformIO, }; @@ -3616,7 +3673,7 @@ mod tests { id: usize, pos: usize, page: &mut PageContent, - record: Record, + record: ImmutableRecord, conn: &Rc, ) -> Vec { let mut payload: Vec = Vec::new(); @@ -3639,7 +3696,8 @@ mod tests { let page = get_page(2); let page = page.get_contents(); let header_size = 8; - let record = Record::new([OwnedValue::Integer(1)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(1))]); let payload = add_record(1, 0, page, record, &conn); assert_eq!(page.cell_count(), 1); let free = compute_free_space(page, 4096); @@ -3667,7 +3725,9 @@ mod tests { let mut cells = Vec::new(); let usable_space = 4096; for i in 0..3 { - let record = Record::new([OwnedValue::Integer(i as i64)].to_vec()); + let record = ImmutableRecord::from_registers(&[Register::OwnedValue( + OwnedValue::Integer(i as i64), + )]); let payload = add_record(i, i, page, record, &conn); assert_eq!(page.cell_count(), i + 1); let free = compute_free_space(page, usable_space); @@ -3892,7 +3952,9 @@ mod tests { ) .unwrap(); let key = OwnedValue::Integer(*key); - let value = Record::new(vec![OwnedValue::Blob(Rc::new(vec![0; *size]))]); + let value = ImmutableRecord::from_registers(&[Register::OwnedValue( + OwnedValue::Blob(vec![0; *size]), + )]); tracing::info!("insert key:{}", key); run_until_done(|| cursor.insert(&key, &value, true), pager.deref()).unwrap(); tracing::info!( @@ -3957,7 +4019,9 @@ mod tests { .unwrap(); let key = OwnedValue::Integer(key); - let value = Record::new(vec![OwnedValue::Blob(Rc::new(vec![0; size]))]); + let value = ImmutableRecord::from_registers(&[Register::OwnedValue( + OwnedValue::Blob(vec![0; size]), + )]); run_until_done(|| cursor.insert(&key, &value, true), pager.deref()).unwrap(); } tracing::info!( @@ -3994,7 +4058,9 @@ mod tests { let usable_space = 4096; let total_cells = 10; for i in 0..total_cells { - let record = Record::new([OwnedValue::Integer(i as i64)].to_vec()); + let record = ImmutableRecord::from_registers(&[Register::OwnedValue( + OwnedValue::Integer(i as i64), + )]); let payload = add_record(i, i, page, record, &conn); assert_eq!(page.cell_count(), i + 1); let free = compute_free_space(page, usable_space); @@ -4352,7 +4418,9 @@ mod tests { let mut cells = Vec::new(); let usable_space = 4096; for i in 0..3 { - let record = Record::new([OwnedValue::Integer(i as i64)].to_vec()); + let record = ImmutableRecord::from_registers(&[Register::OwnedValue( + OwnedValue::Integer(i as i64), + )]); let payload = add_record(i, i, page, record, &conn); assert_eq!(page.cell_count(), i + 1); let free = compute_free_space(page, usable_space); @@ -4392,7 +4460,9 @@ mod tests { let usable_space = 4096; let total_cells = 10; for i in 0..total_cells { - let record = Record::new([OwnedValue::Integer(i as i64)].to_vec()); + let record = ImmutableRecord::from_registers(&[Register::OwnedValue( + OwnedValue::Integer(i as i64), + )]); let payload = add_record(i, i, page, record, &conn); assert_eq!(page.cell_count(), i + 1); let free = compute_free_space(page, usable_space); @@ -4448,7 +4518,9 @@ mod tests { // allow appends with extra place to insert let cell_idx = rng.next_u64() as usize % (page.cell_count() + 1); let free = compute_free_space(page, usable_space); - let record = Record::new([OwnedValue::Integer(i as i64)].to_vec()); + let record = ImmutableRecord::from_registers(&[Register::OwnedValue( + OwnedValue::Integer(i as i64), + )]); let mut payload: Vec = Vec::new(); fill_cell_payload( page.page_type(), @@ -4517,7 +4589,9 @@ mod tests { // allow appends with extra place to insert let cell_idx = rng.next_u64() as usize % (page.cell_count() + 1); let free = compute_free_space(page, usable_space); - let record = Record::new([OwnedValue::Integer(i as i64)].to_vec()); + let record = ImmutableRecord::from_registers(&[Register::OwnedValue( + OwnedValue::Integer(i as i64), + )]); let mut payload: Vec = Vec::new(); fill_cell_payload( page.page_type(), @@ -4571,7 +4645,8 @@ mod tests { let header_size = 8; let usable_space = 4096; - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let payload = add_record(0, 0, page, record, &conn); let free = compute_free_space(page, usable_space); assert_eq!(free, 4096 - payload.len() as u16 - 2 - header_size); @@ -4586,7 +4661,8 @@ mod tests { let page = page.get_contents(); let usable_space = 4096; - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let payload = add_record(0, 0, page, record, &conn); assert_eq!(page.cell_count(), 1); @@ -4611,20 +4687,18 @@ mod tests { let page = page.get_contents(); let usable_space = 4096; - let record = Record::new( - [ - OwnedValue::Integer(0), - OwnedValue::Text(Text::new("aaaaaaaa")), - ] - .to_vec(), - ); + let record = ImmutableRecord::from_registers(&[ + Register::OwnedValue(OwnedValue::Integer(0)), + Register::OwnedValue(OwnedValue::Text(Text::new("aaaaaaaa"))), + ]); let _ = add_record(0, 0, page, record, &conn); assert_eq!(page.cell_count(), 1); drop_cell(page, 0, usable_space).unwrap(); assert_eq!(page.cell_count(), 0); - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let payload = add_record(0, 0, page, record, &conn); assert_eq!(page.cell_count(), 1); @@ -4647,13 +4721,10 @@ mod tests { let page = page.get_contents(); let usable_space = 4096; - let record = Record::new( - [ - OwnedValue::Integer(0), - OwnedValue::Text(Text::new("aaaaaaaa")), - ] - .to_vec(), - ); + let record = ImmutableRecord::from_registers(&[ + Register::OwnedValue(OwnedValue::Integer(0)), + Register::OwnedValue(OwnedValue::Text(Text::new("aaaaaaaa"))), + ]); let _ = add_record(0, 0, page, record, &conn); for _ in 0..100 { @@ -4661,7 +4732,8 @@ mod tests { drop_cell(page, 0, usable_space).unwrap(); assert_eq!(page.cell_count(), 0); - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let payload = add_record(0, 0, page, record, &conn); assert_eq!(page.cell_count(), 1); @@ -4685,11 +4757,14 @@ mod tests { let page = page.get_contents(); let usable_space = 4096; - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let payload = add_record(0, 0, page, record, &conn); - let record = Record::new([OwnedValue::Integer(1)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(1))]); let _ = add_record(1, 1, page, record, &conn); - let record = Record::new([OwnedValue::Integer(2)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(2))]); let _ = add_record(2, 2, page, record, &conn); drop_cell(page, 1, usable_space).unwrap(); @@ -4707,21 +4782,25 @@ mod tests { let page = page.get_contents(); let usable_space = 4096; - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let _ = add_record(0, 0, page, record, &conn); - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let _ = add_record(0, 0, page, record, &conn); drop_cell(page, 0, usable_space).unwrap(); defragment_page(page, usable_space); - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let _ = add_record(0, 1, page, record, &conn); drop_cell(page, 0, usable_space).unwrap(); - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let _ = add_record(0, 1, page, record, &conn); } @@ -4733,7 +4812,8 @@ mod tests { let page = get_page(2); let usable_space = 4096; let insert = |pos, page| { - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let _ = add_record(0, pos, page, record, &conn); }; let drop = |pos, page| { @@ -4772,7 +4852,8 @@ mod tests { let page = get_page(2); let usable_space = 4096; let insert = |pos, page| { - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let _ = add_record(0, pos, page, record, &conn); }; let drop = |pos, page| { @@ -4781,7 +4862,8 @@ mod tests { let defragment = |page| { defragment_page(page, usable_space); }; - let record = Record::new([OwnedValue::Integer(0)].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(0))]); let mut payload: Vec = Vec::new(); fill_cell_payload( page.get_contents().page_type(), @@ -4815,7 +4897,8 @@ mod tests { let mut cursor = BTreeCursor::new(None, pager.clone(), root_page); tracing::info!("INSERT INTO t VALUES ({});", i,); let key = OwnedValue::Integer(i); - let value = Record::new(vec![OwnedValue::Integer(i)]); + let value = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Integer(i))]); tracing::trace!("before insert {}", i); run_until_done( || { @@ -4850,7 +4933,11 @@ mod tests { let page = get_page(2); let usable_space = 4096; - let record = Record::new([OwnedValue::Blob(Rc::new(vec![0; 3600]))].to_vec()); + let record = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Blob(vec![ + 0; + 3600 + ]))]); let mut payload: Vec = Vec::new(); fill_cell_payload( page.get_contents().page_type(), @@ -4886,7 +4973,9 @@ mod tests { for i in 1..=10000 { let mut cursor = BTreeCursor::new(None, pager.clone(), root_page); let key = OwnedValue::Integer(i); - let value = Record::new(vec![OwnedValue::Text(Text::new("hello world"))]); + let value = ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Text( + Text::new("hello world"), + ))]); run_until_done( || { @@ -4957,13 +5046,11 @@ mod tests { let mut cursor = BTreeCursor::new(None, pager.clone(), root_page); tracing::info!("INSERT INTO t VALUES ({});", i,); let key = OwnedValue::Integer(i as i64); - let value = Record::new( - [OwnedValue::Text(Text { - value: Rc::new(huge_texts[i].as_bytes().to_vec()), + let value = + ImmutableRecord::from_registers(&[Register::OwnedValue(OwnedValue::Text(Text { + value: huge_texts[i].as_bytes().to_vec(), subtype: crate::types::TextSubtype::Text, - })] - .to_vec(), - ); + }))]); tracing::trace!("before insert {}", i); tracing::debug!( "=========== btree before ===========\n{}\n\n", @@ -4986,8 +5073,7 @@ mod tests { let mut cursor = BTreeCursor::new(None, pager.clone(), root_page); cursor.move_to_root(); for i in 0..iterations { - let (rowid, _) = - run_until_done(|| cursor.get_next_record(None), pager.deref()).unwrap(); + let rowid = run_until_done(|| cursor.get_next_record(None), pager.deref()).unwrap(); assert_eq!(rowid.unwrap(), i as u64, "got!=expected"); } } diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 02c5b63d2..636b1acaf 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -51,6 +51,7 @@ use crate::types::{ImmutableRecord, RawSlice, RefValue, TextRef, TextSubtype}; use crate::{File, Result}; use parking_lot::RwLock; use std::cell::RefCell; +use std::mem::MaybeUninit; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; @@ -1053,17 +1054,51 @@ pub fn validate_serial_type(value: u64) -> Result { } } -pub fn read_record(payload: &[u8]) -> Result { +struct SmallVec { + pub data: [std::mem::MaybeUninit; 64], + pub len: usize, + pub extra_data: Option>, +} + +impl SmallVec { + pub fn new() -> Self { + Self { + data: unsafe { std::mem::MaybeUninit::uninit().assume_init() }, + len: 0, + extra_data: None, + } + } + + pub fn push(&mut self, value: T) { + if self.len < self.data.len() { + self.data[self.len] = MaybeUninit::new(value); + self.len += 1; + } else { + if self.extra_data.is_none() { + self.extra_data = Some(Vec::new()); + } + self.extra_data.as_mut().unwrap().push(value); + self.len += 1; + } + } +} + +pub fn read_record(payload: &[u8], reuse_immutable: &mut ImmutableRecord) -> Result<()> { + // Let's clear previous use + reuse_immutable.invalidate(); + // Copy payload to ImmutableRecord in order to make RefValue that point to this new buffer. + // By reusing this immutable record we make it less allocation expensive. + reuse_immutable.start_serialization(payload); + let mut pos = 0; let (header_size, nr) = read_varint(payload)?; assert!((header_size as usize) >= nr); let mut header_size = (header_size as usize) - nr; - let payload = payload.to_vec(); pos += nr; - let mut serial_types = Vec::with_capacity(header_size); + let mut serial_types = SmallVec::new(); while header_size > 0 { - let (serial_type, nr) = read_varint(&payload[pos..])?; + let (serial_type, nr) = read_varint(&reuse_immutable.get_payload()[pos..])?; let serial_type = validate_serial_type(serial_type)?; serial_types.push(serial_type); pos += nr; @@ -1071,21 +1106,27 @@ pub fn read_record(payload: &[u8]) -> Result { header_size -= nr; } - let mut values = Vec::with_capacity(serial_types.len()); - for &serial_type in &serial_types { - let (value, n) = read_value(&payload[pos..], serial_type)?; + for &serial_type in &serial_types.data[..serial_types.len.min(serial_types.data.len())] { + let (value, n) = read_value(&reuse_immutable.get_payload()[pos..], unsafe { + *serial_type.as_ptr() + })?; pos += n; - values.push(value); + reuse_immutable.add_value(value); + } + if let Some(extra) = serial_types.extra_data.as_ref() { + for serial_type in extra { + let (value, n) = read_value(&reuse_immutable.get_payload()[pos..], *serial_type)?; + pos += n; + reuse_immutable.add_value(value); + } } - Ok(ImmutableRecord { - payload: std::pin::Pin::new(payload), - values, - }) + Ok(()) } /// Reads a value that might reference the buffer it is reading from. Be sure to store RefValue with the buffer /// always. +#[inline(always)] pub fn read_value(buf: &[u8], serial_type: SerialType) -> Result<(RefValue, usize)> { if serial_type.is_null() { return Ok((RefValue::Null, 0)); @@ -1223,6 +1264,7 @@ pub fn read_value(buf: &[u8], serial_type: SerialType) -> Result<(RefValue, usiz crate::bail_corrupt_error!("Invalid serial type: {}", serial_type) } +#[inline(always)] pub fn read_varint(buf: &[u8]) -> Result<(u64, usize)> { let mut v: u64 = 0; for i in 0..8 { @@ -1582,7 +1624,7 @@ mod tests { #[case] expected: OwnedValue, ) { let result = read_value(buf, serial_type).unwrap(); - assert_eq!(result.0, expected); + assert_eq!(result.0.to_owned(), expected); } #[test] diff --git a/core/types.rs b/core/types.rs index 3636c36c2..a34bf4e65 100644 --- a/core/types.rs +++ b/core/types.rs @@ -6,12 +6,9 @@ use crate::pseudo::PseudoCursor; use crate::storage::btree::BTreeCursor; use crate::storage::sqlite3_ondisk::write_varint; use crate::vdbe::sorter::Sorter; -use crate::vdbe::VTabOpaqueCursor; +use crate::vdbe::{Register, VTabOpaqueCursor}; use crate::Result; -use std::cmp::Ordering; use std::fmt::Display; -use std::pin::Pin; -use std::rc::Rc; const MAX_REAL_SIZE: u8 = 15; @@ -33,7 +30,7 @@ pub enum TextSubtype { #[derive(Debug, Clone, PartialEq)] pub struct Text { - pub value: Rc>, + pub value: Vec, pub subtype: TextSubtype, } @@ -50,14 +47,14 @@ impl Text { pub fn new(value: &str) -> Self { Self { - value: Rc::new(value.as_bytes().to_vec()), + value: value.as_bytes().to_vec(), subtype: TextSubtype::Text, } } pub fn json(value: String) -> Self { Self { - value: Rc::new(value.into_bytes()), + value: value.into_bytes(), subtype: TextSubtype::Json, } } @@ -71,13 +68,23 @@ impl Text { } } +impl TextRef { + pub fn as_str(&self) -> &str { + unsafe { std::str::from_utf8_unchecked(self.value.to_slice()) } + } + + pub fn to_string(&self) -> String { + self.as_str().to_string() + } +} + #[derive(Debug, Clone)] pub enum OwnedValue { Null, Integer(i64), Float(f64), Text(Text), - Blob(Rc>), + Blob(Vec), } #[derive(Debug, Clone, PartialEq)] @@ -86,7 +93,7 @@ pub struct RawSlice { len: usize, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum RefValue { Null, Integer(i64), @@ -109,7 +116,7 @@ impl OwnedValue { } pub fn from_blob(data: Vec) -> Self { - OwnedValue::Blob(std::rc::Rc::new(data)) + OwnedValue::Blob(data) } pub fn to_text(&self) -> Option<&str> { @@ -132,6 +139,26 @@ impl OwnedValue { OwnedValue::Blob(_) => OwnedValueType::Blob, } } + pub fn serialize_serial(&self, out: &mut Vec) { + match self { + OwnedValue::Null => {} + OwnedValue::Integer(i) => { + let serial_type = SerialType::from(self); + match serial_type { + SerialType::I8 => out.extend_from_slice(&(*i as i8).to_be_bytes()), + SerialType::I16 => out.extend_from_slice(&(*i as i16).to_be_bytes()), + SerialType::I24 => out.extend_from_slice(&(*i as i32).to_be_bytes()[1..]), // remove most significant byte + SerialType::I32 => out.extend_from_slice(&(*i as i32).to_be_bytes()), + SerialType::I48 => out.extend_from_slice(&i.to_be_bytes()[2..]), // remove 2 most significant bytes + SerialType::I64 => out.extend_from_slice(&i.to_be_bytes()), + _ => unreachable!(), + } + } + OwnedValue::Float(f) => out.extend_from_slice(&f.to_be_bytes()), + OwnedValue::Text(t) => out.extend_from_slice(&t.value), + OwnedValue::Blob(b) => out.extend_from_slice(b), + }; + } } #[derive(Debug, Clone, PartialEq)] @@ -310,7 +337,7 @@ impl OwnedValue { let Some(blob) = v.to_blob() else { return Ok(OwnedValue::Null); }; - Ok(OwnedValue::Blob(Rc::new(blob))) + Ok(OwnedValue::Blob(blob)) } ExtValueType::Error => { let Some(err) = v.to_error_details() else { @@ -562,33 +589,33 @@ impl std::ops::DivAssign for OwnedValue { } pub trait FromValue<'a> { - fn from_value(value: &'a OwnedValue) -> Result + fn from_value(value: &'a RefValue) -> Result where Self: Sized + 'a; } impl<'a> FromValue<'a> for i64 { - fn from_value(value: &'a OwnedValue) -> Result { + fn from_value(value: &'a RefValue) -> Result { match value { - OwnedValue::Integer(i) => Ok(*i), + RefValue::Integer(i) => Ok(*i), _ => Err(LimboError::ConversionError("Expected integer value".into())), } } } impl<'a> FromValue<'a> for String { - fn from_value(value: &'a OwnedValue) -> Result { + fn from_value(value: &'a RefValue) -> Result { match value { - OwnedValue::Text(s) => Ok(s.as_str().to_string()), + RefValue::Text(s) => Ok(s.as_str().to_string()), _ => Err(LimboError::ConversionError("Expected text value".into())), } } } impl<'a> FromValue<'a> for &'a str { - fn from_value(value: &'a OwnedValue) -> Result { + fn from_value(value: &'a RefValue) -> Result { match value { - OwnedValue::Text(s) => Ok(s.as_str()), + RefValue::Text(s) => Ok(s.as_str()), _ => Err(LimboError::ConversionError("Expected text value".into())), } } @@ -597,10 +624,15 @@ impl<'a> FromValue<'a> for &'a str { /// This struct serves the purpose of not allocating multiple vectors of bytes if not needed. /// A value in a record that has already been serialized can stay serialized and what this struct offsers /// is easy acces to each value which point to the payload. +/// The name might be contradictory as it is immutable in the sense that you cannot modify the values without modifying the payload. #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct ImmutableRecord { - pub payload: Pin>, // << point to this + // We have to be super careful with this buffer since we make values point to the payload we need to take care reallocations + // happen in a controlled manner. If we realocate with values that should be correct, they will now point to undefined data. + // We don't use pin here because it would make it imposible to reuse the buffer if we need to push a new record in the same struct. + payload: Vec, pub values: Vec, + recreating: bool, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -609,10 +641,10 @@ pub struct Record { } impl Record { - pub fn get<'a, T: FromValue<'a> + 'a>(&'a self, idx: usize) -> Result { - let value = &self.values[idx]; - T::from_value(value) - } + // pub fn get<'a, T: FromValue<'a> + 'a>(&'a self, idx: usize) -> Result { + // let value = &self.values[idx]; + // T::from_value(value) + // } pub fn count(&self) -> usize { self.values.len() @@ -634,12 +666,54 @@ impl Record { self.values.len() } } +struct AppendWriter<'a> { + buf: &'a mut Vec, + pos: usize, + buf_capacity_start: usize, + buf_ptr_start: *const u8, +} + +impl<'a> AppendWriter<'a> { + pub fn new(buf: &'a mut Vec, pos: usize) -> Self { + let buf_ptr_start = buf.as_ptr(); + let buf_capacity_start = buf.capacity(); + Self { + buf, + pos, + buf_capacity_start, + buf_ptr_start, + } + } + + #[inline] + pub fn extend_from_slice(&mut self, slice: &[u8]) { + self.buf[self.pos..self.pos + slice.len()].copy_from_slice(slice); + self.pos += slice.len(); + } + + fn assert_finish_capacity(&self) { + // let's make sure we didn't reallocate anywhere else + assert_eq!(self.buf_capacity_start, self.buf.capacity()); + assert_eq!(self.buf_ptr_start, self.buf.as_ptr()); + } +} impl ImmutableRecord { - // pub fn get<'a, T: FromValue<'a> + 'a>(&'a self, idx: usize) -> Result { - // let value = &self.values[idx]; - // T::from_value(value) - // } + pub fn new(payload_capacity: usize, value_capacity: usize) -> Self { + Self { + payload: Vec::with_capacity(payload_capacity), + values: Vec::with_capacity(value_capacity), + recreating: false, + } + } + + pub fn get<'a, T: FromValue<'a> + 'a>(&'a self, idx: usize) -> Result { + let value = self + .values + .get(idx) + .ok_or(LimboError::InternalError("Index out of bounds".into()))?; + T::from_value(value) + } pub fn count(&self) -> usize { self.values.len() @@ -660,19 +734,229 @@ impl ImmutableRecord { pub fn len(&self) -> usize { self.values.len() } + + pub fn from_registers(registers: &[Register]) -> Self { + let mut values = Vec::with_capacity(registers.len()); + let mut serials = Vec::with_capacity(registers.len()); + let mut size_header = 0; + let mut size_values = 0; + + let mut serial_type_buf = [0; 9]; + // write serial types + for value in registers { + let value = value.get_owned_value(); + let serial_type = SerialType::from(value); + let n = write_varint(&mut serial_type_buf[0..], serial_type.into()); + serials.push((serial_type_buf, n)); + + let value_size = match serial_type { + SerialType::Null => 0, + SerialType::I8 => 1, + SerialType::I16 => 2, + SerialType::I24 => 3, + SerialType::I32 => 4, + SerialType::I48 => 6, + SerialType::I64 => 8, + SerialType::F64 => 8, + SerialType::Text { content_size } => content_size, + SerialType::Blob { content_size } => content_size, + }; + + size_header += n; + size_values += value_size; + } + let mut header_size = size_header; + const MIN_HEADER_SIZE: usize = 126; + if header_size <= MIN_HEADER_SIZE { + // common case + // This case means the header size can be contained by a single byte, therefore + // header_size == size of serial types + 1 byte from the header size + // Since header_size is a varint, and a varint the first bit is used to represent we have more bytes to read, + // header size here will be 126 == (2^7 - 1) + header_size += 1; + } else { + todo!("calculate big header size extra bytes"); + // get header varint len + // header_size += n; + // if( nVarint { + values.push(RefValue::Null); + } + OwnedValue::Integer(i) => { + values.push(RefValue::Integer(*i)); + let serial_type = SerialType::from(value); + match serial_type { + SerialType::I8 => writer.extend_from_slice(&(*i as i8).to_be_bytes()), + SerialType::I16 => writer.extend_from_slice(&(*i as i16).to_be_bytes()), + SerialType::I24 => { + writer.extend_from_slice(&(*i as i32).to_be_bytes()[1..]) + } // remove most significant byte + SerialType::I32 => writer.extend_from_slice(&(*i as i32).to_be_bytes()), + SerialType::I48 => writer.extend_from_slice(&i.to_be_bytes()[2..]), // remove 2 most significant bytes + SerialType::I64 => writer.extend_from_slice(&i.to_be_bytes()), + _ => unreachable!(), + } + } + OwnedValue::Float(f) => { + values.push(RefValue::Float(*f)); + writer.extend_from_slice(&f.to_be_bytes()) + } + OwnedValue::Text(t) => { + writer.extend_from_slice(&t.value); + let end_offset = writer.pos; + let len = end_offset - start_offset; + let ptr = unsafe { writer.buf.as_ptr().add(start_offset) }; + let value = RefValue::Text(TextRef { + value: RawSlice::new(ptr, len), + subtype: t.subtype.clone(), + }); + values.push(value); + } + OwnedValue::Blob(b) => { + writer.extend_from_slice(b); + let end_offset = writer.pos; + let len = end_offset - start_offset; + let ptr = unsafe { writer.buf.as_ptr().add(start_offset) }; + values.push(RefValue::Blob(RawSlice::new(ptr, len))); + } + }; + } + + writer.assert_finish_capacity(); + Self { + payload: buf, + values, + recreating: false, + } + } + + pub fn start_serialization(&mut self, payload: &[u8]) { + self.recreating = true; + self.payload.extend_from_slice(payload); + } + pub fn end_serialization(&mut self) { + assert!(self.recreating); + self.recreating = false; + } + + pub fn add_value(&mut self, value: RefValue) { + assert!(self.recreating); + self.values.push(value); + } + + pub fn invalidate(&mut self) { + self.payload.clear(); + self.values.clear(); + } + + pub fn get_payload(&self) -> &[u8] { + &self.payload + } +} + +impl Clone for ImmutableRecord { + fn clone(&self) -> Self { + let mut new_values = Vec::new(); + let new_payload = self.payload.clone(); + for value in &self.values { + let value = match value { + RefValue::Null => RefValue::Null, + RefValue::Integer(i) => RefValue::Integer(*i), + RefValue::Float(f) => RefValue::Float(*f), + RefValue::Text(text_ref) => { + // let's update pointer + let ptr_start = self.payload.as_ptr() as usize; + let ptr_end = text_ref.value.data as usize; + let len = ptr_end - ptr_start; + let new_ptr = unsafe { new_payload.as_ptr().add(len) }; + RefValue::Text(TextRef { + value: RawSlice::new(new_ptr, text_ref.value.len), + subtype: text_ref.subtype.clone(), + }) + } + RefValue::Blob(raw_slice) => { + let ptr_start = self.payload.as_ptr() as usize; + let ptr_end = raw_slice.data as usize; + let len = ptr_end - ptr_start; + let new_ptr = unsafe { new_payload.as_ptr().add(len) }; + RefValue::Blob(RawSlice::new(new_ptr, raw_slice.len)) + } + }; + new_values.push(value); + } + Self { + payload: new_payload, + values: new_values, + recreating: self.recreating, + } + } } impl RefValue { + pub fn to_ffi(&self) -> ExtValue { + match self { + Self::Null => ExtValue::null(), + Self::Integer(i) => ExtValue::from_integer(*i), + Self::Float(fl) => ExtValue::from_float(*fl), + Self::Text(text) => ExtValue::from_text( + std::str::from_utf8(text.value.to_slice()) + .unwrap() + .to_string(), + ), + Self::Blob(blob) => ExtValue::from_blob(blob.to_slice().to_vec()), + } + } + pub fn to_owned(&self) -> OwnedValue { match self { RefValue::Null => OwnedValue::Null, RefValue::Integer(i) => OwnedValue::Integer(*i), RefValue::Float(f) => OwnedValue::Float(*f), RefValue::Text(text_ref) => OwnedValue::Text(Text { - value: Rc::new(text_ref.value.to_slice().to_vec()), + value: text_ref.value.to_slice().to_vec(), subtype: text_ref.subtype.clone(), }), - RefValue::Blob(b) => OwnedValue::Blob(Rc::new(b.to_slice().to_vec())), + RefValue::Blob(b) => OwnedValue::Blob(b.to_slice().to_vec()), + } + } + pub fn to_blob(&self) -> Option<&[u8]> { + match self { + Self::Blob(blob) => Some(blob.to_slice()), + _ => None, + } + } +} + +impl Display for RefValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Null => write!(f, "NULL"), + Self::Integer(i) => write!(f, "{}", i), + Self::Float(fl) => write!(f, "{:?}", fl), + Self::Text(s) => write!(f, "{}", s.as_str()), + Self::Blob(b) => write!(f, "{}", String::from_utf8_lossy(b.to_slice())), } } } @@ -724,153 +1008,8 @@ impl PartialOrd for RefValue { } } -#[allow(clippy::non_canonical_partial_ord_impl)] -impl PartialOrd for RefValue { - fn partial_cmp(&self, other: &OwnedValue) -> Option { - match (self, other) { - (Self::Integer(int_left), OwnedValue::Integer(int_right)) => { - int_left.partial_cmp(int_right) - } - (Self::Integer(int_left), OwnedValue::Float(float_right)) => { - (*int_left as f64).partial_cmp(float_right) - } - (Self::Float(float_left), OwnedValue::Integer(int_right)) => { - float_left.partial_cmp(&(*int_right as f64)) - } - (Self::Float(float_left), OwnedValue::Float(float_right)) => { - float_left.partial_cmp(float_right) - } - // Numeric vs Text/Blob - (Self::Integer(_) | Self::Float(_), OwnedValue::Text(_) | OwnedValue::Blob(_)) => { - Some(std::cmp::Ordering::Less) - } - (Self::Text(_) | Self::Blob(_), OwnedValue::Integer(_) | OwnedValue::Float(_)) => { - Some(std::cmp::Ordering::Greater) - } - - (Self::Text(text_left), OwnedValue::Text(text_right)) => { - let text_left = text_left.value.to_slice(); - text_left.partial_cmp(&text_right.value) - } - // Text vs Blob - (Self::Text(_), OwnedValue::Blob(_)) => Some(std::cmp::Ordering::Less), - (Self::Blob(_), OwnedValue::Text(_)) => Some(std::cmp::Ordering::Greater), - - (Self::Blob(blob_left), OwnedValue::Blob(blob_right)) => { - let blob_left = blob_left.to_slice(); - blob_left.partial_cmp(blob_right) - } - (Self::Null, OwnedValue::Null) => Some(std::cmp::Ordering::Equal), - (Self::Null, _) => Some(std::cmp::Ordering::Less), - (_, OwnedValue::Null) => Some(std::cmp::Ordering::Greater), - } - } -} - -#[allow(clippy::non_canonical_partial_ord_impl)] -impl PartialOrd for OwnedValue { - fn partial_cmp(&self, other: &RefValue) -> Option { - match (self, other) { - (Self::Integer(int_left), RefValue::Integer(int_right)) => { - int_left.partial_cmp(int_right) - } - (Self::Integer(int_left), RefValue::Float(float_right)) => { - (*int_left as f64).partial_cmp(float_right) - } - (Self::Float(float_left), RefValue::Integer(int_right)) => { - float_left.partial_cmp(&(*int_right as f64)) - } - (Self::Float(float_left), RefValue::Float(float_right)) => { - float_left.partial_cmp(float_right) - } - // Numeric vs Text/Blob - (Self::Integer(_) | Self::Float(_), RefValue::Text(_) | RefValue::Blob(_)) => { - Some(std::cmp::Ordering::Less) - } - (Self::Text(_) | Self::Blob(_), RefValue::Integer(_) | RefValue::Float(_)) => { - Some(std::cmp::Ordering::Greater) - } - - (Self::Text(text_left), RefValue::Text(text_right)) => { - let text_right = text_right.value.to_slice(); - text_left.value.as_slice().partial_cmp(text_right) - } - // Text vs Blob - (Self::Text(_), RefValue::Blob(_)) => Some(std::cmp::Ordering::Less), - (Self::Blob(_), RefValue::Text(_)) => Some(std::cmp::Ordering::Greater), - - (Self::Blob(blob_left), RefValue::Blob(blob_right)) => { - let blob_right = blob_right.to_slice(); - blob_left.as_slice().partial_cmp(blob_right) - } - (Self::Null, RefValue::Null) => Some(std::cmp::Ordering::Equal), - (Self::Null, _) => Some(std::cmp::Ordering::Less), - (_, RefValue::Null) => Some(std::cmp::Ordering::Greater), - } - } -} - -impl PartialEq for OwnedValue { - fn eq(&self, other: &RefValue) -> bool { - match (self, other) { - (Self::Integer(int_left), RefValue::Integer(int_right)) => int_left == int_right, - (Self::Float(float_left), RefValue::Float(float_right)) => float_left == float_right, - (Self::Text(text_left), RefValue::Text(text_right)) => { - text_left.value.as_slice() == text_right.value.to_slice() - } - (Self::Blob(blob_left), RefValue::Blob(blob_right)) => { - blob_left.as_slice() == blob_right.to_slice() - } - (Self::Null, RefValue::Null) => true, - _ => false, - } - } -} - -impl PartialEq for RefValue { - fn eq(&self, other: &OwnedValue) -> bool { - match (self, other) { - (Self::Integer(int_left), OwnedValue::Integer(int_right)) => int_left == int_right, - (Self::Float(float_left), OwnedValue::Float(float_right)) => float_left == float_right, - (Self::Text(text_left), OwnedValue::Text(text_right)) => { - text_left.value.to_slice() == text_right.value.as_slice() - } - (Self::Blob(blob_left), OwnedValue::Blob(blob_right)) => { - blob_left.to_slice() == blob_right.as_slice() - } - (Self::Null, OwnedValue::Null) => true, - _ => false, - } - } -} - -pub fn compare_record_to_immutable( - record: &[OwnedValue], - immutable: &[RefValue], -) -> std::cmp::Ordering { - for (a, b) in record.iter().zip(immutable.iter()) { - match a.partial_cmp(b).unwrap() { - Ordering::Equal => {} - order => { - return order; - } - } - } - Ordering::Equal -} -pub fn compare_immutable_to_record( - immutable: &[RefValue], - record: &[OwnedValue], -) -> std::cmp::Ordering { - for (a, b) in immutable.iter().zip(record.iter()) { - match a.partial_cmp(b).unwrap() { - Ordering::Equal => {} - order => { - return order; - } - } - } - Ordering::Equal +pub fn compare_immutable(l: &[RefValue], r: &[RefValue]) -> std::cmp::Ordering { + l.partial_cmp(r).unwrap() } const I8_LOW: i64 = -128; @@ -1063,7 +1202,7 @@ pub enum SeekOp { #[derive(Clone, PartialEq, Debug)] pub enum SeekKey<'a> { TableRowId(u64), - IndexKey(&'a Record), + IndexKey(&'a ImmutableRecord), } impl RawSlice { @@ -1082,7 +1221,6 @@ impl RawSlice { #[cfg(test)] mod tests { use super::*; - use std::rc::Rc; #[test] fn test_serialize_null() { @@ -1217,7 +1355,7 @@ mod tests { #[test] fn test_serialize_blob() { - let blob = Rc::new(vec![1, 2, 3, 4, 5]); + let blob = vec![1, 2, 3, 4, 5]; let record = Record::new(vec![OwnedValue::Blob(blob.clone())]); let mut buf = Vec::new(); record.serialize(&mut buf); diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 613cd66ea..67333c334 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -672,7 +672,7 @@ pub fn insn_to_str( 0, *dest as i32, 0, - OwnedValue::Blob(Rc::new(value.clone())), + OwnedValue::Blob(value.clone()), 0, format!( "r[{}]={} (len={})", diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index f51effe47..ce9346c14 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -40,7 +40,8 @@ use crate::storage::wal::CheckpointResult; use crate::storage::{btree::BTreeCursor, pager::Pager}; use crate::translate::plan::{ResultSetColumn, TableReference}; use crate::types::{ - AggContext, Cursor, CursorResult, ExternalAggState, OwnedValue, Record, SeekKey, SeekOp, + compare_immutable, AggContext, Cursor, CursorResult, ExternalAggState, ImmutableRecord, + OwnedValue, SeekKey, SeekOp, }; use crate::util::{ cast_real_to_integer, cast_text_to_integer, cast_text_to_numeric, cast_text_to_real, @@ -49,7 +50,7 @@ use crate::util::{ use crate::vdbe::builder::CursorType; use crate::vdbe::insn::Insn; use crate::vector::{vector32, vector64, vector_distance_cos, vector_extract}; -use crate::{bail_constraint_error, info, CheckpointStatus}; +use crate::{bail_constraint_error, info, CheckpointStatus, RefValue}; #[cfg(feature = "json")] use crate::{ function::JsonFunc, json::get_json, json::is_json_valid, json::json_array, @@ -235,7 +236,14 @@ enum HaltState { pub enum Register { OwnedValue(OwnedValue), Aggregate(AggContext), - Record(Record), + Record(ImmutableRecord), +} + +/// A row is a the list of registers that hold the values for a filtered row. This row is a pointer, therefore +/// after stepping again, row will be invalidated to be sure it doesn't point to somewhere unexpected. +pub struct Row { + values: *const Register, + count: usize, } /// The program state describes the environment in which the program executes. @@ -243,7 +251,7 @@ pub struct ProgramState { pub pc: InsnReference, cursors: RefCell>>, registers: Vec, - pub(crate) result_row: Option, + pub(crate) result_row: Option, last_compare: Option, deferred_seek: Option<(CursorID, CursorID)>, ended_coroutine: Bitfield<4>, // flag to indicate that a coroutine has ended (key is the yield register. currently we assume that the yield register is always between 0-255, YOLO) @@ -402,6 +410,8 @@ impl Program { if state.is_interrupted() { return Ok(StepResult::Interrupt); } + // invalidate row + let _ = state.result_row.take(); let insn = &self.insns[state.pc as usize]; trace_insn(self, state.pc as InsnReference, insn); match insn { @@ -1179,17 +1189,38 @@ impl Program { ); let cursor = cursor.as_btree_mut(); let record = cursor.record(); - if let Some(record) = record.as_ref() { + let value = if let Some(record) = record.as_ref() { if cursor.get_null_flag() { - OwnedValue::Null + RefValue::Null } else { - record.get_value(*column).to_owned() + record.get_value(*column).clone() } } else { - OwnedValue::Null - } + RefValue::Null + }; + value }; - state.registers[*dest] = Register::OwnedValue(value); + // If we are copying a text/blob, let's try to simply update size of text if we need to allocate more and reuse. + match (&value, &mut state.registers[*dest]) { + ( + RefValue::Text(text_ref), + Register::OwnedValue(OwnedValue::Text(text_reg)), + ) => { + text_reg.value.clear(); + text_reg.value.extend_from_slice(text_ref.value.to_slice()); + } + ( + RefValue::Blob(raw_slice), + Register::OwnedValue(OwnedValue::Blob(blob_reg)), + ) => { + blob_reg.clear(); + blob_reg.extend_from_slice(raw_slice.to_slice()); + } + _ => { + let reg = &mut state.registers[*dest]; + *reg = Register::OwnedValue(value.to_owned()); + } + } } CursorType::Sorter => { let record = { @@ -1199,7 +1230,7 @@ impl Program { }; if let Some(record) = record { state.registers[*dest] = - Register::OwnedValue(record.get_value(*column).clone()); + Register::OwnedValue(record.get_value(*column).to_owned()); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } @@ -1209,7 +1240,7 @@ impl Program { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_pseudo_mut(); if let Some(record) = cursor.record() { - record.get_value(*column).clone() + record.get_value(*column).to_owned() } else { OwnedValue::Null } @@ -1230,13 +1261,17 @@ impl Program { count, dest_reg, } => { - let record = make_owned_record(&state.registers, start_reg, count); + let record = make_record(&state.registers, start_reg, count); state.registers[*dest_reg] = Register::Record(record); state.pc += 1; } Insn::ResultRow { start_reg, count } => { - let record = make_owned_record(&state.registers, start_reg, count); - state.result_row = Some(record); + let row = Row { + values: &state.registers[*start_reg] as *const Register, + count: *count, + }; + + state.result_row = Some(row); state.pc += 1; return Ok(StepResult::Row); } @@ -1435,8 +1470,7 @@ impl Program { state.pc += 1; } Insn::Blob { value, dest } => { - state.registers[*dest] = - Register::OwnedValue(OwnedValue::Blob(Rc::new(value.clone()))); + state.registers[*dest] = Register::OwnedValue(OwnedValue::Blob(value.clone())); state.pc += 1; } Insn::RowId { cursor_id, dest } => { @@ -1547,7 +1581,7 @@ impl Program { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); let record_from_regs = - make_owned_record(&state.registers, start_reg, num_regs); + make_record(&state.registers, start_reg, num_regs); let found = return_if_io!( cursor.seek(SeekKey::IndexKey(&record_from_regs), SeekOp::GE) ); @@ -1605,8 +1639,8 @@ impl Program { let found = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); - let record_from_regs: Record = - make_owned_record(&state.registers, start_reg, num_regs); + let record_from_regs = + make_record(&state.registers, start_reg, num_regs); let found = return_if_io!( cursor.seek(SeekKey::IndexKey(&record_from_regs), SeekOp::GT) ); @@ -1663,15 +1697,14 @@ impl Program { let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); - let record_from_regs: Record = - make_owned_record(&state.registers, start_reg, num_regs); + let record_from_regs = make_record(&state.registers, start_reg, num_regs); let pc = if let Some(ref idx_record) = *cursor.record() { // Compare against the same number of values - if idx_record.get_values()[..record_from_regs.len()] - .iter() - .zip(&record_from_regs.get_values()[..]) - .all(|(a, b)| a >= b) - { + let ord = compare_immutable( + &idx_record.get_values()[..record_from_regs.len()], + &record_from_regs.get_values(), + ); + if ord.is_ge() { target_pc.to_offset_int() } else { state.pc + 1 @@ -1693,8 +1726,7 @@ impl Program { let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); - let record_from_regs: Record = - make_owned_record(&state.registers, start_reg, num_regs); + let record_from_regs = make_record(&state.registers, start_reg, num_regs); let pc = if let Some(ref idx_record) = *cursor.record() { // Compare against the same number of values if idx_record.get_values()[..record_from_regs.len()] @@ -1723,8 +1755,7 @@ impl Program { let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); - let record_from_regs: Record = - make_owned_record(&state.registers, start_reg, num_regs); + let record_from_regs = make_record(&state.registers, start_reg, num_regs); let pc = if let Some(ref idx_record) = *cursor.record() { // Compare against the same number of values if idx_record.get_values()[..record_from_regs.len()] @@ -1753,8 +1784,7 @@ impl Program { let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); - let record_from_regs: Record = - make_owned_record(&state.registers, start_reg, num_regs); + let record_from_regs = make_record(&state.registers, start_reg, num_regs); let pc = if let Some(ref idx_record) = *cursor.record() { // Compare against the same number of values if idx_record.get_values()[..record_from_regs.len()] @@ -3606,12 +3636,8 @@ fn get_new_rowid(cursor: &mut BTreeCursor, mut rng: R) -> Result Record { - let mut values = Vec::with_capacity(*count); - for r in registers.iter().skip(*start_reg).take(*count) { - values.push(r.get_owned_value().clone()) - } - Record::new(values) +fn make_record(registers: &[Register], start_reg: &usize, count: &usize) -> ImmutableRecord { + ImmutableRecord::from_registers(®isters[*start_reg..*start_reg + *count]) } fn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) { @@ -3915,7 +3941,7 @@ fn exec_randomblob(reg: &OwnedValue) -> OwnedValue { let mut blob: Vec = vec![0; length]; getrandom::getrandom(&mut blob).expect("Failed to generate random blob"); - OwnedValue::Blob(Rc::new(blob)) + OwnedValue::Blob(blob) } fn exec_quote(value: &OwnedValue) -> OwnedValue { @@ -4067,7 +4093,7 @@ fn exec_instr(reg: &OwnedValue, pattern: &OwnedValue) -> OwnedValue { if let (OwnedValue::Blob(reg), OwnedValue::Blob(pattern)) = (reg, pattern) { let result = reg .windows(pattern.len()) - .position(|window| window == **pattern) + .position(|window| window == *pattern) .map_or(0, |i| i + 1); return OwnedValue::Integer(result as i64); } @@ -4124,7 +4150,7 @@ fn exec_unhex(reg: &OwnedValue, ignored_chars: Option<&OwnedValue>) -> OwnedValu OwnedValue::Null => OwnedValue::Null, _ => match ignored_chars { None => match hex::decode(reg.to_string()) { - Ok(bytes) => OwnedValue::Blob(Rc::new(bytes)), + Ok(bytes) => OwnedValue::Blob(bytes), Err(_) => OwnedValue::Null, }, Some(ignore) => match ignore { @@ -4136,7 +4162,7 @@ fn exec_unhex(reg: &OwnedValue, ignored_chars: Option<&OwnedValue>) -> OwnedValu .trim_end_matches(|x| pat.contains(x)) .to_string(); match hex::decode(trimmed) { - Ok(bytes) => OwnedValue::Blob(Rc::new(bytes)), + Ok(bytes) => OwnedValue::Blob(bytes), Err(_) => OwnedValue::Null, } } @@ -4247,7 +4273,7 @@ fn exec_zeroblob(req: &OwnedValue) -> OwnedValue { OwnedValue::Text(s) => s.as_str().parse().unwrap_or(0), _ => 0, }; - OwnedValue::Blob(Rc::new(vec![0; length.max(0) as usize])) + OwnedValue::Blob(vec![0; length.max(0) as usize]) } // exec_if returns whether you should jump @@ -4271,7 +4297,7 @@ fn exec_cast(value: &OwnedValue, datatype: &str) -> OwnedValue { // Convert to TEXT first, then interpret as BLOB // TODO: handle encoding let text = value.to_string(); - OwnedValue::Blob(Rc::new(text.into_bytes())) + OwnedValue::Blob(text.into_bytes()) } // TEXT To cast a BLOB value to TEXT, the sequence of bytes that make up the BLOB is interpreted as text encoded using the database encoding. // Casting an INTEGER or REAL value into TEXT renders the value as if via sqlite3_snprintf() except that the resulting TEXT uses the encoding of the database connection. @@ -4473,6 +4499,75 @@ fn exec_math_log(arg: &OwnedValue, base: Option<&OwnedValue>) -> OwnedValue { OwnedValue::Float(result) } +pub trait FromValueRow<'a> { + fn from_value(value: &'a OwnedValue) -> Result + where + Self: Sized + 'a; +} + +impl<'a> FromValueRow<'a> for i64 { + fn from_value(value: &'a OwnedValue) -> Result { + match value { + OwnedValue::Integer(i) => Ok(*i), + _ => Err(LimboError::ConversionError("Expected integer value".into())), + } + } +} + +impl<'a> FromValueRow<'a> for String { + fn from_value(value: &'a OwnedValue) -> Result { + match value { + OwnedValue::Text(s) => Ok(s.as_str().to_string()), + _ => Err(LimboError::ConversionError("Expected text value".into())), + } + } +} + +impl<'a> FromValueRow<'a> for &'a str { + fn from_value(value: &'a OwnedValue) -> Result { + match value { + OwnedValue::Text(s) => Ok(s.as_str()), + _ => Err(LimboError::ConversionError("Expected text value".into())), + } + } +} + +impl<'a> FromValueRow<'a> for &'a OwnedValue { + fn from_value(value: &'a OwnedValue) -> Result { + Ok(value) + } +} + +impl Row { + pub fn get<'a, T: FromValueRow<'a> + 'a>(&'a self, idx: usize) -> Result { + let value = unsafe { self.values.add(idx).as_ref().unwrap() }; + let value = match value { + Register::OwnedValue(owned_value) => owned_value, + _ => unreachable!("a row should be formed of values only"), + }; + T::from_value(value) + } + + pub fn get_value<'a>(&'a self, idx: usize) -> &'a OwnedValue { + let value = unsafe { self.values.add(idx).as_ref().unwrap() }; + let value = match value { + Register::OwnedValue(owned_value) => owned_value, + _ => unreachable!("a row should be formed of values only"), + }; + value + } + + pub fn get_values(&self) -> impl Iterator { + let values = unsafe { std::slice::from_raw_parts(self.values, self.count) }; + // This should be ownedvalues + // TODO: add check for this + values.iter().map(|v| v.get_owned_value()) + } + + pub fn len(&self) -> usize { + self.count + } +} #[cfg(test)] mod tests { use crate::vdbe::{exec_replace, Register}; @@ -4484,7 +4579,7 @@ mod tests { exec_unhex, exec_unicode, exec_upper, exec_zeroblob, execute_sqlite_version, Bitfield, OwnedValue, }; - use std::{collections::HashMap, rc::Rc}; + use std::collections::HashMap; #[test] fn test_length() { @@ -4500,7 +4595,7 @@ mod tests { let expected_len = OwnedValue::Integer(7); assert_eq!(exec_length(&input_float), expected_len); - let expected_blob = OwnedValue::Blob(Rc::new("example".as_bytes().to_vec())); + let expected_blob = OwnedValue::Blob("example".as_bytes().to_vec()); let expected_len = OwnedValue::Integer(7); assert_eq!(exec_length(&expected_blob), expected_len); } @@ -4538,7 +4633,7 @@ mod tests { let expected: OwnedValue = OwnedValue::build_text("text"); assert_eq!(exec_typeof(&input), expected); - let input = OwnedValue::Blob(Rc::new("limbo".as_bytes().to_vec())); + let input = OwnedValue::Blob("limbo".as_bytes().to_vec()); let expected: OwnedValue = OwnedValue::build_text("blob"); assert_eq!(exec_typeof(&input), expected); } @@ -4572,7 +4667,7 @@ mod tests { ); assert_eq!(exec_unicode(&OwnedValue::Null), OwnedValue::Null); assert_eq!( - exec_unicode(&OwnedValue::Blob(Rc::new("example".as_bytes().to_vec()))), + exec_unicode(&OwnedValue::Blob("example".as_bytes().to_vec())), OwnedValue::Integer(101) ); } @@ -4732,11 +4827,11 @@ mod tests { #[test] fn test_unhex() { let input = OwnedValue::build_text("6f"); - let expected = OwnedValue::Blob(Rc::new(vec![0x6f])); + let expected = OwnedValue::Blob(vec![0x6f]); assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::build_text("6f"); - let expected = OwnedValue::Blob(Rc::new(vec![0x6f])); + let expected = OwnedValue::Blob(vec![0x6f]); assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::build_text("611"); @@ -4744,7 +4839,7 @@ mod tests { assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::build_text(""); - let expected = OwnedValue::Blob(Rc::new(vec![])); + let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::build_text("61x"); @@ -5131,23 +5226,23 @@ mod tests { let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::Blob(Rc::new(vec![1, 2, 3, 4, 5])); - let pattern = OwnedValue::Blob(Rc::new(vec![3, 4])); + let input = OwnedValue::Blob(vec![1, 2, 3, 4, 5]); + let pattern = OwnedValue::Blob(vec![3, 4]); let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::Blob(Rc::new(vec![1, 2, 3, 4, 5])); - let pattern = OwnedValue::Blob(Rc::new(vec![3, 2])); + let input = OwnedValue::Blob(vec![1, 2, 3, 4, 5]); + let pattern = OwnedValue::Blob(vec![3, 2]); let expected = OwnedValue::Integer(0); assert_eq!(exec_instr(&input, &pattern), expected); - let input = OwnedValue::Blob(Rc::new(vec![0x61, 0x62, 0x63, 0x64, 0x65])); + let input = OwnedValue::Blob(vec![0x61, 0x62, 0x63, 0x64, 0x65]); let pattern = OwnedValue::build_text("cd"); let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text("abcde"); - let pattern = OwnedValue::Blob(Rc::new(vec![0x63, 0x64])); + let pattern = OwnedValue::Blob(vec![0x63, 0x64]); let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); } @@ -5198,19 +5293,19 @@ mod tests { let expected = Some(OwnedValue::Integer(0)); assert_eq!(exec_sign(&input), expected); - let input = OwnedValue::Blob(Rc::new(b"abc".to_vec())); + let input = OwnedValue::Blob(b"abc".to_vec()); let expected = Some(OwnedValue::Null); assert_eq!(exec_sign(&input), expected); - let input = OwnedValue::Blob(Rc::new(b"42".to_vec())); + let input = OwnedValue::Blob(b"42".to_vec()); let expected = Some(OwnedValue::Integer(1)); assert_eq!(exec_sign(&input), expected); - let input = OwnedValue::Blob(Rc::new(b"-42".to_vec())); + let input = OwnedValue::Blob(b"-42".to_vec()); let expected = Some(OwnedValue::Integer(-1)); assert_eq!(exec_sign(&input), expected); - let input = OwnedValue::Blob(Rc::new(b"0".to_vec())); + let input = OwnedValue::Blob(b"0".to_vec()); let expected = Some(OwnedValue::Integer(0)); assert_eq!(exec_sign(&input), expected); @@ -5222,39 +5317,39 @@ mod tests { #[test] fn test_exec_zeroblob() { let input = OwnedValue::Integer(0); - let expected = OwnedValue::Blob(Rc::new(vec![])); + let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Null; - let expected = OwnedValue::Blob(Rc::new(vec![])); + let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Integer(4); - let expected = OwnedValue::Blob(Rc::new(vec![0; 4])); + let expected = OwnedValue::Blob(vec![0; 4]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Integer(-1); - let expected = OwnedValue::Blob(Rc::new(vec![])); + let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::build_text("5"); - let expected = OwnedValue::Blob(Rc::new(vec![0; 5])); + let expected = OwnedValue::Blob(vec![0; 5]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::build_text("-5"); - let expected = OwnedValue::Blob(Rc::new(vec![])); + let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::build_text("text"); - let expected = OwnedValue::Blob(Rc::new(vec![])); + let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Float(2.6); - let expected = OwnedValue::Blob(Rc::new(vec![0; 2])); + let expected = OwnedValue::Blob(vec![0; 2]); assert_eq!(exec_zeroblob(&input), expected); - let input = OwnedValue::Blob(Rc::new(vec![1])); - let expected = OwnedValue::Blob(Rc::new(vec![])); + let input = OwnedValue::Blob(vec![1]); + let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); } diff --git a/core/vdbe/sorter.rs b/core/vdbe/sorter.rs index 60f839b1c..584a29271 100644 --- a/core/vdbe/sorter.rs +++ b/core/vdbe/sorter.rs @@ -1,9 +1,9 @@ -use crate::types::Record; +use crate::types::ImmutableRecord; use std::cmp::Ordering; pub struct Sorter { - records: Vec, - current: Option, + records: Vec, + current: Option, order: Vec, } @@ -51,11 +51,11 @@ impl Sorter { pub fn next(&mut self) { self.current = self.records.pop(); } - pub fn record(&self) -> Option<&Record> { + pub fn record(&self) -> Option<&ImmutableRecord> { self.current.as_ref() } - pub fn insert(&mut self, record: &Record) { - self.records.push(Record::new(record.get_values().to_vec())); + pub fn insert(&mut self, record: &ImmutableRecord) { + self.records.push(record.clone()); } } diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index 299ae7310..f54e6bdb2 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::missing_safety_doc)] #![allow(non_camel_case_types)] +use limbo_core::OwnedValue; use log::trace; use std::ffi::{self, CStr, CString}; @@ -636,8 +637,8 @@ pub unsafe extern "C" fn sqlite3_column_text( Some(row) => row, None => return std::ptr::null(), }; - match row.get_values().get(idx as usize) { - Some(limbo_core::OwnedValue::Text(text)) => text.as_str().as_ptr(), + match row.get::<&OwnedValue>(idx as usize) { + Ok(limbo_core::OwnedValue::Text(text)) => text.as_str().as_ptr(), _ => std::ptr::null(), } } diff --git a/tests/integration/fuzz/mod.rs b/tests/integration/fuzz/mod.rs index 92f10ebab..f776fc9a7 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -72,12 +72,13 @@ mod tests { }; let row = row .get_values() - .iter() .map(|x| match x { limbo_core::OwnedValue::Null => rusqlite::types::Value::Null, limbo_core::OwnedValue::Integer(x) => rusqlite::types::Value::Integer(*x), limbo_core::OwnedValue::Float(x) => rusqlite::types::Value::Real(*x), - limbo_core::OwnedValue::Text(x) => rusqlite::types::Value::Text(x.to_string()), + limbo_core::OwnedValue::Text(x) => { + rusqlite::types::Value::Text(x.as_str().to_string()) + } limbo_core::OwnedValue::Blob(x) => rusqlite::types::Value::Blob(x.to_vec()), }) .collect(); diff --git a/tests/integration/query_processing/test_read_path.rs b/tests/integration/query_processing/test_read_path.rs index b90d8f9d4..7f525f6a9 100644 --- a/tests/integration/query_processing/test_read_path.rs +++ b/tests/integration/query_processing/test_read_path.rs @@ -15,7 +15,10 @@ fn test_statement_reset_bind() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - assert_eq!(*row.get_value(0), limbo_core::OwnedValue::Integer(1)); + assert_eq!( + *row.get::<&OwnedValue>(0).unwrap(), + limbo_core::OwnedValue::Integer(1) + ); } StepResult::IO => tmp_db.io.run_once()?, _ => break, @@ -30,7 +33,10 @@ fn test_statement_reset_bind() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - assert_eq!(*row.get_value(0), limbo_core::OwnedValue::Integer(2)); + assert_eq!( + *row.get::<&OwnedValue>(0).unwrap(), + limbo_core::OwnedValue::Integer(2) + ); } StepResult::IO => tmp_db.io.run_once()?, _ => break, @@ -63,23 +69,23 @@ fn test_statement_bind() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - if let limbo_core::OwnedValue::Text(s) = row.get_value(0) { + if let limbo_core::OwnedValue::Text(s) = row.get::<&OwnedValue>(0).unwrap() { assert_eq!(s.as_str(), "hello") } - if let limbo_core::OwnedValue::Text(s) = row.get_value(1) { + if let limbo_core::OwnedValue::Text(s) = row.get::<&OwnedValue>(1).unwrap() { assert_eq!(s.as_str(), "hello") } - if let limbo_core::OwnedValue::Integer(i) = row.get_value(2) { + if let limbo_core::OwnedValue::Integer(i) = row.get::<&OwnedValue>(2).unwrap() { assert_eq!(*i, 42) } - if let limbo_core::OwnedValue::Blob(v) = row.get_value(3) { - assert_eq!(v.as_ref(), &vec![0x1 as u8, 0x2, 0x3]) + if let limbo_core::OwnedValue::Blob(v) = row.get::<&OwnedValue>(3).unwrap() { + assert_eq!(v.as_slice(), &vec![0x1 as u8, 0x2, 0x3]) } - if let limbo_core::OwnedValue::Float(f) = row.get_value(4) { + if let limbo_core::OwnedValue::Float(f) = row.get::<&OwnedValue>(4).unwrap() { assert_eq!(*f, 0.5) } } diff --git a/tests/integration/query_processing/test_write_path.rs b/tests/integration/query_processing/test_write_path.rs index 2e9941045..e948ed5d1 100644 --- a/tests/integration/query_processing/test_write_path.rs +++ b/tests/integration/query_processing/test_write_path.rs @@ -1,6 +1,6 @@ use crate::common::{self, maybe_setup_tracing}; use crate::common::{compare_string, do_flush, TempDatabase}; -use limbo_core::{Connection, StepResult}; +use limbo_core::{Connection, OwnedValue, StepResult}; use log::debug; use std::rc::Rc; @@ -44,17 +44,8 @@ fn test_simple_overflow_page() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.get_value(0); - let text = row.get_value(1); - let id = match first_value { - limbo_core::OwnedValue::Integer(i) => *i as i32, - limbo_core::OwnedValue::Float(f) => *f as i32, - _ => unreachable!(), - }; - let text = match text { - limbo_core::OwnedValue::Text(t) => t.as_str(), - _ => unreachable!(), - }; + let id = row.get::(0).unwrap(); + let text = row.get::<&str>(0).unwrap(); assert_eq!(1, id); compare_string(&huge_text, text); } @@ -120,17 +111,8 @@ fn test_sequential_overflow_page() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.get_value(0); - let text = row.get_value(1); - let id = match first_value { - limbo_core::OwnedValue::Integer(i) => *i as i32, - limbo_core::OwnedValue::Float(f) => *f as i32, - _ => unreachable!(), - }; - let text = match text { - limbo_core::OwnedValue::Text(t) => t.as_str(), - _ => unreachable!(), - }; + let id = row.get::(0).unwrap(); + let text = row.get::(1).unwrap(); let huge_text = &huge_texts[current_index]; compare_string(huge_text, text); assert_eq!(current_index, id as usize); @@ -154,6 +136,7 @@ fn test_sequential_overflow_page() -> anyhow::Result<()> { } #[test_log::test] +#[ignore = "this takes too long :)"] fn test_sequential_write() -> anyhow::Result<()> { let _ = env_logger::try_init(); maybe_setup_tracing(); @@ -192,7 +175,7 @@ fn test_sequential_write() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.get_values().first().expect("missing id"); + let first_value = row.get::<&OwnedValue>(0).expect("missing id"); let id = match first_value { limbo_core::OwnedValue::Integer(i) => *i as i32, limbo_core::OwnedValue::Float(f) => *f as i32, @@ -258,9 +241,9 @@ fn test_regression_multi_row_insert() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.get_values().first().expect("missing id"); + let first_value = row.get::<&OwnedValue>(0).expect("missing id"); let id = match first_value { - limbo_core::OwnedValue::Float(f) => *f as i32, + OwnedValue::Float(f) => *f as i32, _ => panic!("expected float"), }; actual_ids.push(id); @@ -304,7 +287,10 @@ fn test_statement_reset() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - assert_eq!(*row.get_value(0), limbo_core::OwnedValue::Integer(1)); + assert_eq!( + *row.get::<&OwnedValue>(0).unwrap(), + limbo_core::OwnedValue::Integer(1) + ); break; } StepResult::IO => tmp_db.io.run_once()?, @@ -318,7 +304,10 @@ fn test_statement_reset() -> anyhow::Result<()> { match stmt.step()? { StepResult::Row => { let row = stmt.row().unwrap(); - assert_eq!(*row.get_value(0), limbo_core::OwnedValue::Integer(1)); + assert_eq!( + *row.get::<&OwnedValue>(0).unwrap(), + limbo_core::OwnedValue::Integer(1) + ); break; } StepResult::IO => tmp_db.io.run_once()?, @@ -368,12 +357,7 @@ fn test_wal_checkpoint() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.get_value(0); - let id = match first_value { - limbo_core::OwnedValue::Integer(i) => *i as i32, - limbo_core::OwnedValue::Float(f) => *f as i32, - _ => unreachable!(), - }; + let id = row.get::(0).unwrap(); assert_eq!(current_index, id as usize); current_index += 1; } @@ -432,13 +416,9 @@ fn test_wal_restart() -> anyhow::Result<()> { match rows.step()? { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.get_value(0); - let count = match first_value { - limbo_core::OwnedValue::Integer(i) => i, - _ => unreachable!(), - }; + let count = row.get::(0).unwrap(); debug!("counted {}", count); - return Ok(*count as usize); + return Ok(count as usize); } StepResult::IO => { tmp_db.io.run_once()?; diff --git a/tests/integration/wal/test_wal.rs b/tests/integration/wal/test_wal.rs index b76ac1773..bb731917a 100644 --- a/tests/integration/wal/test_wal.rs +++ b/tests/integration/wal/test_wal.rs @@ -81,11 +81,7 @@ fn test_wal_1_writer_1_reader() -> Result<()> { match rows.step().unwrap() { StepResult::Row => { let row = rows.row().unwrap(); - let first_value = row.get_value(0); - let id = match first_value { - limbo_core::OwnedValue::Integer(i) => *i as i32, - _ => unreachable!(), - }; + let id = row.get::(0).unwrap(); assert_eq!(id, i); i += 1; }