mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-01 07:24:19 +01:00
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:
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {}",
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(' ');
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
496
core/types.rs
496
core/types.rs
@@ -6,12 +6,9 @@ use crate::pseudo::PseudoCursor;
|
||||
use crate::storage::btree::BTreeCursor;
|
||||
use crate::storage::sqlite3_ondisk::write_varint;
|
||||
use crate::vdbe::sorter::Sorter;
|
||||
use crate::vdbe::VTabOpaqueCursor;
|
||||
use crate::vdbe::{Register, VTabOpaqueCursor};
|
||||
use crate::Result;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
const MAX_REAL_SIZE: u8 = 15;
|
||||
|
||||
@@ -33,7 +30,7 @@ pub enum TextSubtype {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Text {
|
||||
pub value: Rc<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);
|
||||
|
||||
@@ -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={})",
|
||||
|
||||
239
core/vdbe/mod.rs
239
core/vdbe/mod.rs
@@ -40,7 +40,8 @@ use crate::storage::wal::CheckpointResult;
|
||||
use crate::storage::{btree::BTreeCursor, pager::Pager};
|
||||
use crate::translate::plan::{ResultSetColumn, TableReference};
|
||||
use crate::types::{
|
||||
AggContext, Cursor, CursorResult, ExternalAggState, OwnedValue, Record, SeekKey, SeekOp,
|
||||
compare_immutable, AggContext, Cursor, CursorResult, ExternalAggState, ImmutableRecord,
|
||||
OwnedValue, SeekKey, SeekOp,
|
||||
};
|
||||
use crate::util::{
|
||||
cast_real_to_integer, cast_text_to_integer, cast_text_to_numeric, cast_text_to_real,
|
||||
@@ -49,7 +50,7 @@ use crate::util::{
|
||||
use crate::vdbe::builder::CursorType;
|
||||
use crate::vdbe::insn::Insn;
|
||||
use crate::vector::{vector32, vector64, vector_distance_cos, vector_extract};
|
||||
use crate::{bail_constraint_error, info, CheckpointStatus};
|
||||
use crate::{bail_constraint_error, info, CheckpointStatus, RefValue};
|
||||
#[cfg(feature = "json")]
|
||||
use crate::{
|
||||
function::JsonFunc, json::get_json, json::is_json_valid, json::json_array,
|
||||
@@ -235,7 +236,14 @@ enum HaltState {
|
||||
pub enum Register {
|
||||
OwnedValue(OwnedValue),
|
||||
Aggregate(AggContext),
|
||||
Record(Record),
|
||||
Record(ImmutableRecord),
|
||||
}
|
||||
|
||||
/// A row is a the list of registers that hold the values for a filtered row. This row is a pointer, therefore
|
||||
/// after stepping again, row will be invalidated to be sure it doesn't point to somewhere unexpected.
|
||||
pub struct Row {
|
||||
values: *const Register,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
/// The program state describes the environment in which the program executes.
|
||||
@@ -243,7 +251,7 @@ pub struct ProgramState {
|
||||
pub pc: InsnReference,
|
||||
cursors: RefCell<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(®isters[*start_reg..*start_reg + *count])
|
||||
}
|
||||
|
||||
fn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) {
|
||||
@@ -3915,7 +3941,7 @@ fn exec_randomblob(reg: &OwnedValue) -> OwnedValue {
|
||||
|
||||
let mut blob: Vec<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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user