Merge 'Allocation improvements with ImmutableRecord, OwnedRecord and read_record' from Pere Diaz Bou

This pr is huge again but I will try to introduce each improvement one
by one.
## Overview
### Remove Rc for Text and Blob.
In general copying is bad, that's why we hid it with `Rc`s. With the
introduction of `ImmutableRecord` we make it less relevant because now
we will copy only once anyways, no other place should copy it so we can
avoid using `Rc`. If we we were to copy it it most likely means where
are doing something wrong.
### Reuse `Text` and `Blob` OwnedValues.
Most of the queries spend time overwriting the same register over and
over. What about we don't allocate new `OwnedValue` and we just simply
reuse the `OwnedValue` and extend the internal buffer. That's what I did
and it worked quite nicely.
### Make `Register::Record` be `ImmutableRecord`
`ImmutableRecord` basically means "serialized record", that's why all
the data is contained in a single payload buffer. There is a list of
values to reference that payload to reduce time complexity of search --
there is an argument to make a record without this vec to reduce memory
footprint.  This improvement I don't think it had a direct impact on
performance but it is a simpler way to lay the memory without any
complicated reference counted pointers, and instead we use a contiguous
piece of memory.
### Make `ImmutableRecord` reusable in `BTreeCursor`.
`BTreeCursor` allocated and deallocated records when it needed a new
one. This is obviously a big waste because we could be reusing the
internal buffer to avoid allocations. `ImmutableRecord` proved to be
useful here because now, we will only store a single `ImmutableRecord`
in the cursor that we will never deallocate -- we will just reallocate
when needed and replace the current one with the next one on demand.
## Return `Row` as a reference of Registers.
A `ResultRow` bytecode takes care of gathering all the columns of a row
and returning them to the user. Previously we could create a new
`Record` struct with all the cloned values which proved to be wasteful.
SQLite is smart about this so we must be as well. Basically a row now is
a wrapper for `struct Row { *const Register, count: usize }`, and we
basically include some QOL methods to avoid using pointers directly.
I know pointers are unsafe. That's why this row will be invalidate on
the next step of the VM and this row should be not used outside there.
### Inlining go brrr
`read_varint` and `read_value` are called in a tight loop making it easy
to see overhead of the call stack. That's why I sprinkled some
`#[inline(always)]` and saw something like a 15% speed boost.
## read_record with custom `SmallVec<T>`
We tend to overuse vectors for everything, this is quite bad because it
requires heap memory allocations. We can avoid this with a simple
`SmallVec` that simply fallsback to a vec with more complex scenarios.
## Benchmarks!
```
### before
fun/limbo » cargo bench -- limbo_execute 2>&1 | grep -B 1 "time: " | tee out.log
Execute `SELECT 1`/limbo_execute_select_1
                        time:   [43.958 ns 44.056 ns 44.154 ns]
--
Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/1
                        time:   [407.82 ns 408.57 ns 409.41 ns]
--
Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/10
                        time:   [2.7335 µs 2.7386 µs 2.7443 µs]
--
Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/50
                        time:   [13.451 µs 13.485 µs 13.520 µs]
--
Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/100
                        time:   [26.967 µs 27.077 µs 27.201 µs]after:
```
### after
```
fun/limbo (more-register) » cargo bench -- limbo_execute 2>&1 | grep -B 1 "time: " | tee out.log                                                                                                                                                                                                                                                        130 ↵
Execute `SELECT 1`/limbo_execute_select_1
                        time:   [33.386 ns 33.440 ns 33.510 ns]
--
Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/1
                        time:   [326.79 ns 327.37 ns 328.03 ns]
--
Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/10
                        time:   [1.5817 µs 1.5849 µs 1.5889 µs]
--
Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/50
                        time:   [7.3295 µs 7.3531 µs 7.3829 µs]
--
Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/100
                        time:   [14.538 µs 14.570 µs 14.606 µs]
```

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #1197
This commit is contained in:
Pekka Enberg
2025-03-30 13:17:16 +03:00
22 changed files with 837 additions and 499 deletions

View File

@@ -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()

View File

@@ -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,
}

View File

@@ -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<JObject<'local>> {
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
}

View File

@@ -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()
}
}
}

View File

@@ -352,7 +352,7 @@ fn py_to_owned_value(obj: &Bound<PyAny>) -> Result<limbo_core::OwnedValue> {
} else if let Ok(string) = obj.extract::<String>() {
return Ok(OwnedValue::Text(Text::from_str(string)));
} else if let Ok(bytes) = obj.downcast::<PyBytes>() {
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::<ProgrammingError, _>(format!(
"Unsupported Python type: {}",

View File

@@ -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),

View File

@@ -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(' ');
}

View File

@@ -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<OwnedValue> {
@@ -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();
}

View File

@@ -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<Ow
let jsonbin = cache.get_or_insert_with(json_value, json_conv_fn);
match jsonbin {
Ok(jsonbin) => 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<OwnedValue> {
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<OwnedValue> {
#[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<u8> = 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];

View File

@@ -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;

View File

@@ -1,7 +1,7 @@
use crate::types::Record;
use crate::types::ImmutableRecord;
pub struct PseudoCursor {
current: Option<Record>,
current: Option<ImmutableRecord>,
}
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);
}
}

View File

@@ -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<Option<u64>>,
record: RefCell<Option<ImmutableRecord>>,
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<Option<ImmutableRecord>>,
empty_record: Cell<bool>,
}
/// 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<CursorResult<(Option<u64>, Option<ImmutableRecord>)>> {
fn get_prev_record(&mut self) -> Result<CursorResult<Option<u64>>> {
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<CursorResult<ImmutableRecord>> {
) -> Result<CursorResult<()>> {
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<CursorResult<(Option<u64>, Option<ImmutableRecord>)>> {
) -> Result<CursorResult<Option<u64>>> {
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<CursorResult<(Option<u64>, Option<ImmutableRecord>)>> {
fn do_seek(&mut self, key: SeekKey<'_>, op: SeekOp) -> Result<CursorResult<Option<u64>>> {
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<CursorResult<()>> {
fn insert_into_page(
&mut self,
key: &OwnedValue,
record: &ImmutableRecord,
) -> Result<CursorResult<()>> {
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<CursorResult<()>> {
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<CursorResult<()>> {
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<CursorResult<()>> {
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<CursorResult<()>> {
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<CursorResult<bool>> {
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<Option<ImmutableRecord>> {
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<CursorResult<()>> {
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<CursorResult<()>> {
// 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<ImmutableRecord>> {
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<ImmutableRecord>> {
self.reusable_immutable_record.borrow_mut()
}
}
impl PageStack {
@@ -3354,7 +3411,7 @@ fn fill_cell_payload(
page_type: PageType,
int_key: Option<u64>,
cell_payload: &mut Vec<u8>,
record: &Record,
record: &ImmutableRecord,
usable_space: u16,
pager: Rc<Pager>,
) {
@@ -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<Connection>,
) -> Vec<u8> {
let mut payload: Vec<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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");
}
}

View File

@@ -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<SerialType> {
}
}
pub fn read_record(payload: &[u8]) -> Result<ImmutableRecord> {
struct SmallVec<T> {
pub data: [std::mem::MaybeUninit<T>; 64],
pub len: usize,
pub extra_data: Option<Vec<T>>,
}
impl<T: Default + Copy> SmallVec<T> {
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<ImmutableRecord> {
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]

View File

@@ -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<Vec<u8>>,
pub value: Vec<u8>,
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<Vec<u8>>),
Blob(Vec<u8>),
}
#[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<u8>) -> 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<u8>) {
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<OwnedValue> for OwnedValue {
}
pub trait FromValue<'a> {
fn from_value(value: &'a OwnedValue) -> Result<Self>
fn from_value(value: &'a RefValue) -> Result<Self>
where
Self: Sized + 'a;
}
impl<'a> FromValue<'a> for i64 {
fn from_value(value: &'a OwnedValue) -> Result<Self> {
fn from_value(value: &'a RefValue) -> Result<Self> {
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<Self> {
fn from_value(value: &'a RefValue) -> Result<Self> {
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<Self> {
fn from_value(value: &'a RefValue) -> Result<Self> {
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<Vec<u8>>, // << 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<u8>,
pub values: Vec<RefValue>,
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<T> {
let value = &self.values[idx];
T::from_value(value)
}
// pub fn get<'a, T: FromValue<'a> + 'a>(&'a self, idx: usize) -> Result<T> {
// 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<u8>,
pos: usize,
buf_capacity_start: usize,
buf_ptr_start: *const u8,
}
impl<'a> AppendWriter<'a> {
pub fn new(buf: &'a mut Vec<u8>, 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<T> {
// 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<T> {
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<sqlite3VarintLen(nHdr) ) nHdr++;
}
// 1. write header size
let mut buf = Vec::new();
buf.reserve_exact(header_size + size_values);
assert_eq!(buf.capacity(), header_size + size_values);
assert!(header_size <= 126);
let n = write_varint(&mut serial_type_buf, header_size as u64);
buf.resize(buf.capacity(), 0);
let mut writer = AppendWriter::new(&mut buf, 0);
writer.extend_from_slice(&serial_type_buf[..n]);
// 2. Write serial
for (value, n) in serials {
writer.extend_from_slice(&value[..n]);
}
// write content
for value in registers {
let value = value.get_owned_value();
let start_offset = writer.pos;
match value {
OwnedValue::Null => {
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<RefValue> for RefValue {
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl PartialOrd<OwnedValue> for RefValue {
fn partial_cmp(&self, other: &OwnedValue) -> Option<std::cmp::Ordering> {
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<RefValue> for OwnedValue {
fn partial_cmp(&self, other: &RefValue) -> Option<std::cmp::Ordering> {
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<RefValue> 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<OwnedValue> 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);

View File

@@ -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={})",

View File

@@ -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<Vec<Option<Cursor>>>,
registers: Vec<Register>,
pub(crate) result_row: Option<Record>,
pub(crate) result_row: Option<Row>,
last_compare: Option<std::cmp::Ordering>,
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<R: Rng>(cursor: &mut BTreeCursor, mut rng: R) -> Result<CursorR
Ok(CursorResult::Ok(rowid.try_into().unwrap()))
}
fn make_owned_record(registers: &[Register], start_reg: &usize, count: &usize) -> 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(&registers[*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<u8> = 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<Self>
where
Self: Sized + 'a;
}
impl<'a> FromValueRow<'a> for i64 {
fn from_value(value: &'a OwnedValue) -> Result<Self> {
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<Self> {
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<Self> {
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<Self> {
Ok(value)
}
}
impl Row {
pub fn get<'a, T: FromValueRow<'a> + 'a>(&'a self, idx: usize) -> Result<T> {
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<Item = &OwnedValue> {
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);
}

View File

@@ -1,9 +1,9 @@
use crate::types::Record;
use crate::types::ImmutableRecord;
use std::cmp::Ordering;
pub struct Sorter {
records: Vec<Record>,
current: Option<Record>,
records: Vec<ImmutableRecord>,
current: Option<ImmutableRecord>,
order: Vec<bool>,
}
@@ -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());
}
}

View File

@@ -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(),
}
}

View File

@@ -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();

View File

@@ -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)
}
}

View File

@@ -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::<i64>(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::<i64>(0).unwrap();
let text = row.get::<String>(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::<i64>(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::<i64>(0).unwrap();
debug!("counted {}", count);
return Ok(*count as usize);
return Ok(count as usize);
}
StepResult::IO => {
tmp_db.io.run_once()?;

View File

@@ -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::<i64>(0).unwrap();
assert_eq!(id, i);
i += 1;
}