#![allow(unused_variables)] use crate::error::{LimboError, SQLITE_CONSTRAINT_PRIMARYKEY}; use crate::ext::ExtValue; use crate::function::{AggFunc, ExtFunc, MathFunc, MathFuncArity, ScalarFunc, VectorFunc}; use crate::functions::datetime::{ exec_date, exec_datetime_full, exec_julianday, exec_strftime, exec_time, exec_unixepoch, }; use crate::functions::printf::exec_printf; use std::{borrow::BorrowMut, rc::Rc}; use crate::pseudo::PseudoCursor; use crate::result::LimboResult; use crate::schema::{affinity, Affinity}; use crate::storage::btree::{BTreeCursor, BTreeKey}; use crate::storage::wal::CheckpointResult; use crate::types::{ AggContext, Cursor, CursorResult, ExternalAggState, OwnedValue, SeekKey, SeekOp, }; use crate::util::{ cast_real_to_integer, cast_text_to_integer, cast_text_to_numeric, cast_text_to_real, checked_cast_text_to_numeric, parse_schema_rows, RoundToPrecision, }; use crate::vdbe::builder::CursorType; use crate::vdbe::insn::{IdxInsertFlags, Insn}; use crate::vector::{vector32, vector64, vector_distance_cos, vector_extract}; use crate::{info, MvCursor, RefValue, Row, StepResult, TransactionState}; use super::insn::{ exec_add, exec_and, exec_bit_and, exec_bit_not, exec_bit_or, exec_boolean_not, exec_concat, exec_divide, exec_multiply, exec_or, exec_remainder, exec_shift_left, exec_shift_right, exec_subtract, Cookie, RegisterOrLiteral, }; use super::HaltState; use rand::thread_rng; use super::likeop::{construct_like_escape_arg, exec_glob, exec_like_with_escape}; use super::sorter::Sorter; use regex::{Regex, RegexBuilder}; use std::cell::RefCell; use std::collections::HashMap; #[cfg(feature = "json")] use crate::{ function::JsonFunc, json::convert_dbtype_to_raw_jsonb, json::get_json, json::is_json_valid, json::json_array, json::json_array_length, json::json_arrow_extract, json::json_arrow_shift_extract, json::json_error_position, json::json_extract, json::json_from_raw_bytes_agg, json::json_insert, json::json_object, json::json_patch, json::json_quote, json::json_remove, json::json_replace, json::json_set, json::json_type, json::jsonb, json::jsonb_array, json::jsonb_extract, json::jsonb_insert, json::jsonb_object, json::jsonb_patch, json::jsonb_remove, json::jsonb_replace, json::jsonb_set, }; use super::{get_new_rowid, make_record, Program, ProgramState, Register}; use crate::{ bail_constraint_error, must_be_btree_cursor, resolve_ext_path, MvStore, Pager, Result, DATABASE_VERSION, }; macro_rules! return_if_io { ($expr:expr) => { match $expr? { CursorResult::Ok(v) => v, CursorResult::IO => return Ok(InsnFunctionStepResult::IO), } }; } pub type InsnFunction = fn( &Program, &mut ProgramState, &Insn, &Rc, Option<&Rc>, ) -> Result; pub enum InsnFunctionStepResult { Done, IO, Row, Interrupt, Busy, Step, } pub fn op_init( _program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Init { target_pc } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); state.pc = target_pc.to_offset_int(); Ok(InsnFunctionStepResult::Step) } pub fn op_add( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Add { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_add( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_subtract( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Subtract { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_subtract( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_multiply( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Multiply { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_multiply( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_divide( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Divide { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_divide( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_remainder( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Remainder { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_remainder( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_bit_and( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::BitAnd { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_bit_and( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_bit_or( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::BitOr { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_bit_or( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_bit_not( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::BitNot { reg, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_bit_not(state.registers[*reg].get_owned_value())); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_checkpoint( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Checkpoint { database: _, checkpoint_mode: _, dest, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let result = program.connection.upgrade().unwrap().checkpoint(); match result { Ok(CheckpointResult { num_wal_frames: num_wal_pages, num_checkpointed_frames: num_checkpointed_pages, }) => { // https://sqlite.org/pragma.html#pragma_wal_checkpoint // 1st col: 1 (checkpoint SQLITE_BUSY) or 0 (not busy). state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(0)); // 2nd col: # modified pages written to wal file state.registers[*dest + 1] = Register::OwnedValue(OwnedValue::Integer(num_wal_pages as i64)); // 3rd col: # pages moved to db after checkpoint state.registers[*dest + 2] = Register::OwnedValue(OwnedValue::Integer(num_checkpointed_pages as i64)); } Err(_err) => state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(1)), } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_null( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Null { dest, dest_end } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let Some(dest_end) = dest_end { for i in *dest..=*dest_end { state.registers[i] = Register::OwnedValue(OwnedValue::Null); } } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_null_row( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::NullRow { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "NullRow"); let cursor = cursor.as_btree_mut(); cursor.set_null_flag(true); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_compare( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Compare { start_reg_a, start_reg_b, count, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let start_reg_a = *start_reg_a; let start_reg_b = *start_reg_b; let count = *count; if start_reg_a + count > start_reg_b { return Err(LimboError::InternalError( "Compare registers overlap".to_string(), )); } let mut cmp = None; for i in 0..count { let a = state.registers[start_reg_a + i].get_owned_value(); let b = state.registers[start_reg_b + i].get_owned_value(); cmp = Some(a.cmp(b)); if cmp != Some(std::cmp::Ordering::Equal) { break; } } state.last_compare = cmp; state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_jump( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Jump { target_pc_lt, target_pc_eq, target_pc_gt, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc_lt.is_offset()); assert!(target_pc_eq.is_offset()); assert!(target_pc_gt.is_offset()); let cmp = state.last_compare.take(); if cmp.is_none() { return Err(LimboError::InternalError( "Jump without compare".to_string(), )); } let target_pc = match cmp.unwrap() { std::cmp::Ordering::Less => *target_pc_lt, std::cmp::Ordering::Equal => *target_pc_eq, std::cmp::Ordering::Greater => *target_pc_gt, }; state.pc = target_pc.to_offset_int(); Ok(InsnFunctionStepResult::Step) } pub fn op_move( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Move { source_reg, dest_reg, count, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let source_reg = *source_reg; let dest_reg = *dest_reg; let count = *count; for i in 0..count { state.registers[dest_reg + i] = std::mem::replace( &mut state.registers[source_reg + i], Register::OwnedValue(OwnedValue::Null), ); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_if_pos( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::IfPos { reg, target_pc, decrement_by, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let reg = *reg; let target_pc = *target_pc; match state.registers[reg].get_owned_value() { OwnedValue::Integer(n) if *n > 0 => { state.pc = target_pc.to_offset_int(); state.registers[reg] = Register::OwnedValue(OwnedValue::Integer(*n - *decrement_by as i64)); } OwnedValue::Integer(_) => { state.pc += 1; } _ => { return Err(LimboError::InternalError( "IfPos: the value in the register is not an integer".into(), )); } } Ok(InsnFunctionStepResult::Step) } pub fn op_not_null( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::NotNull { reg, target_pc } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let reg = *reg; let target_pc = *target_pc; match &state.registers[reg].get_owned_value() { OwnedValue::Null => { state.pc += 1; } _ => { state.pc = target_pc.to_offset_int(); } } Ok(InsnFunctionStepResult::Step) } pub fn op_eq( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Eq { lhs, rhs, target_pc, flags, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let lhs = *lhs; let rhs = *rhs; let target_pc = *target_pc; let cond = *state.registers[lhs].get_owned_value() == *state.registers[rhs].get_owned_value(); let nulleq = flags.has_nulleq(); let jump_if_null = flags.has_jump_if_null(); match ( &state.registers[lhs].get_owned_value(), &state.registers[rhs].get_owned_value(), ) { (_, OwnedValue::Null) | (OwnedValue::Null, _) => { if (nulleq && cond) || (!nulleq && jump_if_null) { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } _ => { if *state.registers[lhs].get_owned_value() == *state.registers[rhs].get_owned_value() { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } } Ok(InsnFunctionStepResult::Step) } pub fn op_ne( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Ne { lhs, rhs, target_pc, flags, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let lhs = *lhs; let rhs = *rhs; let target_pc = *target_pc; let cond = *state.registers[lhs].get_owned_value() != *state.registers[rhs].get_owned_value(); let nulleq = flags.has_nulleq(); let jump_if_null = flags.has_jump_if_null(); match ( &state.registers[lhs].get_owned_value(), &state.registers[rhs].get_owned_value(), ) { (_, OwnedValue::Null) | (OwnedValue::Null, _) => { if (nulleq && cond) || (!nulleq && jump_if_null) { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } _ => { if *state.registers[lhs].get_owned_value() != *state.registers[rhs].get_owned_value() { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } } Ok(InsnFunctionStepResult::Step) } pub fn op_lt( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Lt { lhs, rhs, target_pc, flags, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let lhs = *lhs; let rhs = *rhs; let target_pc = *target_pc; let jump_if_null = flags.has_jump_if_null(); match ( &state.registers[lhs].get_owned_value(), &state.registers[rhs].get_owned_value(), ) { (_, OwnedValue::Null) | (OwnedValue::Null, _) => { if jump_if_null { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } _ => { if *state.registers[lhs].get_owned_value() < *state.registers[rhs].get_owned_value() { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } } Ok(InsnFunctionStepResult::Step) } pub fn op_le( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Le { lhs, rhs, target_pc, flags, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let lhs = *lhs; let rhs = *rhs; let target_pc = *target_pc; let jump_if_null = flags.has_jump_if_null(); match ( &state.registers[lhs].get_owned_value(), &state.registers[rhs].get_owned_value(), ) { (_, OwnedValue::Null) | (OwnedValue::Null, _) => { if jump_if_null { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } _ => { if *state.registers[lhs].get_owned_value() <= *state.registers[rhs].get_owned_value() { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } } Ok(InsnFunctionStepResult::Step) } pub fn op_gt( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Gt { lhs, rhs, target_pc, flags, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let lhs = *lhs; let rhs = *rhs; let target_pc = *target_pc; let jump_if_null = flags.has_jump_if_null(); match ( &state.registers[lhs].get_owned_value(), &state.registers[rhs].get_owned_value(), ) { (_, OwnedValue::Null) | (OwnedValue::Null, _) => { if jump_if_null { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } _ => { if *state.registers[lhs].get_owned_value() > *state.registers[rhs].get_owned_value() { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } } Ok(InsnFunctionStepResult::Step) } pub fn op_ge( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Ge { lhs, rhs, target_pc, flags, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let lhs = *lhs; let rhs = *rhs; let target_pc = *target_pc; let jump_if_null = flags.has_jump_if_null(); match ( &state.registers[lhs].get_owned_value(), &state.registers[rhs].get_owned_value(), ) { (_, OwnedValue::Null) | (OwnedValue::Null, _) => { if jump_if_null { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } _ => { if *state.registers[lhs].get_owned_value() >= *state.registers[rhs].get_owned_value() { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } } Ok(InsnFunctionStepResult::Step) } pub fn op_if( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::If { reg, target_pc, jump_if_null, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); if exec_if( &state.registers[*reg].get_owned_value(), *jump_if_null, false, ) { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_if_not( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::IfNot { reg, target_pc, jump_if_null, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); if exec_if( &state.registers[*reg].get_owned_value(), *jump_if_null, true, ) { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_open_read_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::OpenReadAsync { cursor_id, root_page, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); let mv_cursor = match state.mv_tx_id { Some(tx_id) => { let table_id = *root_page as u64; let mv_store = mv_store.unwrap().clone(); let mv_cursor = Rc::new(RefCell::new( MvCursor::new(mv_store.clone(), tx_id, table_id).unwrap(), )); Some(mv_cursor) } None => None, }; let cursor = BTreeCursor::new(mv_cursor, pager.clone(), *root_page); let mut cursors = state.cursors.borrow_mut(); match cursor_type { CursorType::BTreeTable(_) => { cursors .get_mut(*cursor_id) .unwrap() .replace(Cursor::new_btree(cursor)); } CursorType::BTreeIndex(_) => { cursors .get_mut(*cursor_id) .unwrap() .replace(Cursor::new_btree(cursor)); } CursorType::Pseudo(_) => { panic!("OpenReadAsync on pseudo cursor"); } CursorType::Sorter => { panic!("OpenReadAsync on sorter cursor"); } CursorType::VirtualTable(_) => { panic!("OpenReadAsync on virtual table cursor, use Insn:VOpenAsync instead"); } } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_open_read_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_vopen_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::VOpenAsync { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); let CursorType::VirtualTable(virtual_table) = cursor_type else { panic!("VOpenAsync on non-virtual table cursor"); }; let cursor = virtual_table.open()?; state .cursors .borrow_mut() .insert(*cursor_id, Some(Cursor::Virtual(cursor))); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_vcreate( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::VCreate { module_name, table_name, args_reg, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let module_name = state.registers[*module_name].get_owned_value().to_string(); let table_name = state.registers[*table_name].get_owned_value().to_string(); let args = if let Some(args_reg) = args_reg { if let Register::Record(rec) = &state.registers[*args_reg] { rec.get_values().iter().map(|v| v.to_ffi()).collect() } else { return Err(LimboError::InternalError( "VCreate: args_reg is not a record".to_string(), )); } } else { vec![] }; let Some(conn) = program.connection.upgrade() else { return Err(crate::LimboError::ExtensionError( "Failed to upgrade Connection".to_string(), )); }; let table = crate::VirtualTable::from_args( Some(&table_name), &module_name, args, &conn.syms.borrow(), limbo_ext::VTabKind::VirtualTable, None, )?; { conn.syms .borrow_mut() .vtabs .insert(table_name, table.clone()); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_vopen_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_vfilter( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::VFilter { cursor_id, pc_if_empty, arg_count, args_reg, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); let CursorType::VirtualTable(virtual_table) = cursor_type else { panic!("VFilter on non-virtual table cursor"); }; let has_rows = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_virtual_mut(); let mut args = Vec::new(); for i in 0..*arg_count { args.push(state.registers[args_reg + i].get_owned_value().clone()); } virtual_table.filter(cursor, *arg_count, args)? }; if !has_rows { state.pc = pc_if_empty.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_vcolumn( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::VColumn { cursor_id, column, dest, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); let CursorType::VirtualTable(virtual_table) = cursor_type else { panic!("VColumn on non-virtual table cursor"); }; let value = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_virtual_mut(); virtual_table.column(cursor, *column)? }; state.registers[*dest] = Register::OwnedValue(value); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_vupdate( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::VUpdate { cursor_id, arg_count, start_reg, conflict_action, .. } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); let CursorType::VirtualTable(virtual_table) = cursor_type else { panic!("VUpdate on non-virtual table cursor"); }; if *arg_count < 2 { return Err(LimboError::InternalError( "VUpdate: arg_count must be at least 2 (rowid and insert_rowid)".to_string(), )); } let mut argv = Vec::with_capacity(*arg_count); for i in 0..*arg_count { if let Some(value) = state.registers.get(*start_reg + i) { argv.push(value.get_owned_value().clone()); } else { return Err(LimboError::InternalError(format!( "VUpdate: register out of bounds at {}", *start_reg + i ))); } } let result = virtual_table.update(&argv); match result { Ok(Some(new_rowid)) => { if *conflict_action == 5 { // ResolveType::Replace if let Some(conn) = program.connection.upgrade() { conn.update_last_rowid(new_rowid as u64); } } state.pc += 1; } Ok(None) => { // no-op or successful update without rowid return state.pc += 1; } Err(e) => { // virtual table update failed return Err(LimboError::ExtensionError(format!( "Virtual table update failed: {}", e ))); } } Ok(InsnFunctionStepResult::Step) } pub fn op_vnext( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::VNext { cursor_id, pc_if_next, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); let CursorType::VirtualTable(virtual_table) = cursor_type else { panic!("VNextAsync on non-virtual table cursor"); }; let has_more = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_virtual_mut(); virtual_table.next(cursor)? }; if has_more { state.pc = pc_if_next.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_open_pseudo( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::OpenPseudo { cursor_id, content_reg: _, num_fields: _, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursors = state.cursors.borrow_mut(); let cursor = PseudoCursor::new(); cursors .get_mut(*cursor_id) .unwrap() .replace(Cursor::new_pseudo(cursor)); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_rewind_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::RewindAsync { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "RewindAsync"); let cursor = cursor.as_btree_mut(); return_if_io!(cursor.rewind()); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_last_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::LastAsync { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "LastAsync"); let cursor = cursor.as_btree_mut(); return_if_io!(cursor.last()); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_last_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::LastAwait { cursor_id, pc_if_empty, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(pc_if_empty.is_offset()); let is_empty = { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "LastAwait"); let cursor = cursor.as_btree_mut(); cursor.wait_for_completion()?; cursor.is_empty() }; if is_empty { state.pc = pc_if_empty.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_rewind_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::RewindAwait { cursor_id, pc_if_empty, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(pc_if_empty.is_offset()); let is_empty = { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "RewindAwait"); let cursor = cursor.as_btree_mut(); cursor.wait_for_completion()?; cursor.is_empty() }; if is_empty { state.pc = pc_if_empty.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_column( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Column { cursor_id, column, dest, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let Some((index_cursor_id, table_cursor_id)) = state.deferred_seek.take() { let deferred_seek = { let rowid = { let mut index_cursor = state.get_cursor(index_cursor_id); let index_cursor = index_cursor.as_btree_mut(); index_cursor.rowid()? }; let mut table_cursor = state.get_cursor(table_cursor_id); let table_cursor = table_cursor.as_btree_mut(); match table_cursor.seek(SeekKey::TableRowId(rowid.unwrap()), SeekOp::EQ)? { CursorResult::Ok(_) => None, CursorResult::IO => Some((index_cursor_id, table_cursor_id)), } }; if let Some(deferred_seek) = deferred_seek { state.deferred_seek = Some(deferred_seek); return Ok(InsnFunctionStepResult::IO); } } let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); match cursor_type { CursorType::BTreeTable(_) | CursorType::BTreeIndex(_) => { let value = { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "Column"); let cursor = cursor.as_btree_mut(); let record = cursor.record(); let value = if let Some(record) = record.as_ref() { if cursor.get_null_flag() { RefValue::Null } else { match record.get_value_opt(*column) { Some(val) => val.clone(), None => RefValue::Null, } } } else { RefValue::Null }; 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 = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_sorter_mut(); cursor.record().cloned() }; if let Some(record) = record { state.registers[*dest] = Register::OwnedValue(match record.get_value_opt(*column) { Some(val) => val.to_owned(), None => OwnedValue::Null, }); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } CursorType::Pseudo(_) => { let value = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_pseudo_mut(); if let Some(record) = cursor.record() { record.get_value(*column).to_owned() } else { OwnedValue::Null } }; state.registers[*dest] = Register::OwnedValue(value); } CursorType::VirtualTable(_) => { panic!("Insn:Column on virtual table cursor, use Insn:VColumn instead"); } } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_make_record( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::MakeRecord { start_reg, count, dest_reg, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let record = make_record(&state.registers, start_reg, count); state.registers[*dest_reg] = Register::Record(record); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_result_row( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::ResultRow { start_reg, count } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let row = Row { values: &state.registers[*start_reg] as *const Register, count: *count, }; state.result_row = Some(row); state.pc += 1; return Ok(InsnFunctionStepResult::Row); } pub fn op_next_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::NextAsync { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "NextAsync"); let cursor = cursor.as_btree_mut(); cursor.set_null_flag(false); return_if_io!(cursor.next()); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_prev_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::PrevAsync { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "PrevAsync"); let cursor = cursor.as_btree_mut(); cursor.set_null_flag(false); return_if_io!(cursor.prev()); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_prev_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::PrevAwait { cursor_id, pc_if_next, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(pc_if_next.is_offset()); let is_empty = { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "PrevAwait"); let cursor = cursor.as_btree_mut(); cursor.wait_for_completion()?; cursor.is_empty() }; if !is_empty { state.pc = pc_if_next.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_next_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::NextAwait { cursor_id, pc_if_next, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(pc_if_next.is_offset()); let is_empty = { let mut cursor = must_be_btree_cursor!(*cursor_id, program.cursor_ref, state, "NextAwait"); let cursor = cursor.as_btree_mut(); cursor.wait_for_completion()?; cursor.is_empty() }; if !is_empty { state.pc = pc_if_next.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_halt( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Halt { err_code, description, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; match *err_code { 0 => {} SQLITE_CONSTRAINT_PRIMARYKEY => { return Err(LimboError::Constraint(format!( "UNIQUE constraint failed: {} (19)", description ))); } _ => { return Err(LimboError::Constraint(format!( "undocumented halt error code {}", description ))); } } match program.halt(pager.clone(), state, mv_store.clone())? { StepResult::Done => Ok(InsnFunctionStepResult::Done), StepResult::IO => Ok(InsnFunctionStepResult::IO), StepResult::Row => Ok(InsnFunctionStepResult::Row), StepResult::Interrupt => Ok(InsnFunctionStepResult::Interrupt), StepResult::Busy => Ok(InsnFunctionStepResult::Busy), } } pub fn op_transaction( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Transaction { write } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let Some(mv_store) = &mv_store { if state.mv_tx_id.is_none() { let tx_id = mv_store.begin_tx(); program .connection .upgrade() .unwrap() .mv_transactions .borrow_mut() .push(tx_id); state.mv_tx_id = Some(tx_id); } } else { let connection = program.connection.upgrade().unwrap(); let current_state = connection.transaction_state.borrow().clone(); let (new_transaction_state, updated) = match (¤t_state, write) { (TransactionState::Write, true) => (TransactionState::Write, false), (TransactionState::Write, false) => (TransactionState::Write, false), (TransactionState::Read, true) => (TransactionState::Write, true), (TransactionState::Read, false) => (TransactionState::Read, false), (TransactionState::None, true) => (TransactionState::Write, true), (TransactionState::None, false) => (TransactionState::Read, true), }; if updated && matches!(current_state, TransactionState::None) { if let LimboResult::Busy = pager.begin_read_tx()? { return Ok(InsnFunctionStepResult::Busy); } } if updated && matches!(new_transaction_state, TransactionState::Write) { if let LimboResult::Busy = pager.begin_write_tx()? { tracing::trace!("begin_write_tx busy"); return Ok(InsnFunctionStepResult::Busy); } } if updated { connection.transaction_state.replace(new_transaction_state); } } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_auto_commit( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::AutoCommit { auto_commit, rollback, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let conn = program.connection.upgrade().unwrap(); if matches!(state.halt_state, Some(HaltState::Checkpointing)) { return match program.halt(pager.clone(), state, mv_store.clone())? { super::StepResult::Done => Ok(InsnFunctionStepResult::Done), super::StepResult::IO => Ok(InsnFunctionStepResult::IO), super::StepResult::Row => Ok(InsnFunctionStepResult::Row), super::StepResult::Interrupt => Ok(InsnFunctionStepResult::Interrupt), super::StepResult::Busy => Ok(InsnFunctionStepResult::Busy), }; } if *auto_commit != *conn.auto_commit.borrow() { if *rollback { todo!("Rollback is not implemented"); } else { conn.auto_commit.replace(*auto_commit); } } else if !*auto_commit { return Err(LimboError::TxError( "cannot start a transaction within a transaction".to_string(), )); } else if *rollback { return Err(LimboError::TxError( "cannot rollback - no transaction is active".to_string(), )); } else { return Err(LimboError::TxError( "cannot commit - no transaction is active".to_string(), )); } return match program.halt(pager.clone(), state, mv_store.clone())? { super::StepResult::Done => Ok(InsnFunctionStepResult::Done), super::StepResult::IO => Ok(InsnFunctionStepResult::IO), super::StepResult::Row => Ok(InsnFunctionStepResult::Row), super::StepResult::Interrupt => Ok(InsnFunctionStepResult::Interrupt), super::StepResult::Busy => Ok(InsnFunctionStepResult::Busy), }; } pub fn op_goto( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Goto { target_pc } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); state.pc = target_pc.to_offset_int(); Ok(InsnFunctionStepResult::Step) } pub fn op_gosub( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Gosub { target_pc, return_reg, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); state.registers[*return_reg] = Register::OwnedValue(OwnedValue::Integer((state.pc + 1) as i64)); state.pc = target_pc.to_offset_int(); Ok(InsnFunctionStepResult::Step) } pub fn op_return( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Return { return_reg } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let OwnedValue::Integer(pc) = state.registers[*return_reg].get_owned_value() { let pc: u32 = (*pc) .try_into() .unwrap_or_else(|_| panic!("Return register is negative: {}", pc)); state.pc = pc; } else { return Err(LimboError::InternalError( "Return register is not an integer".to_string(), )); } Ok(InsnFunctionStepResult::Step) } pub fn op_integer( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Integer { value, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(*value)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_real( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Real { value, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(OwnedValue::Float(*value)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_real_affinity( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::RealAffinity { register } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let OwnedValue::Integer(i) = &state.registers[*register].get_owned_value() { state.registers[*register] = Register::OwnedValue(OwnedValue::Float(*i as f64)); }; state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_string8( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::String8 { value, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(OwnedValue::build_text(value)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_blob( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Blob { value, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(OwnedValue::Blob(value.clone())); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_row_id( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::RowId { cursor_id, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let Some((index_cursor_id, table_cursor_id)) = state.deferred_seek.take() { let deferred_seek = { let rowid = { let mut index_cursor = state.get_cursor(index_cursor_id); let index_cursor = index_cursor.as_btree_mut(); let rowid = index_cursor.rowid()?; rowid }; let mut table_cursor = state.get_cursor(table_cursor_id); let table_cursor = table_cursor.as_btree_mut(); let deferred_seek = match table_cursor.seek(SeekKey::TableRowId(rowid.unwrap()), SeekOp::EQ)? { CursorResult::Ok(_) => None, CursorResult::IO => Some((index_cursor_id, table_cursor_id)), }; deferred_seek }; if let Some(deferred_seek) = deferred_seek { state.deferred_seek = Some(deferred_seek); return Ok(InsnFunctionStepResult::IO); } } let mut cursors = state.cursors.borrow_mut(); if let Some(Cursor::BTree(btree_cursor)) = cursors.get_mut(*cursor_id).unwrap() { if let Some(ref rowid) = btree_cursor.rowid()? { state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(*rowid as i64)); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } else if let Some(Cursor::Virtual(virtual_cursor)) = cursors.get_mut(*cursor_id).unwrap() { let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); let CursorType::VirtualTable(virtual_table) = cursor_type else { panic!("VUpdate on non-virtual table cursor"); }; let rowid = virtual_table.rowid(virtual_cursor); if rowid != 0 { state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(rowid)); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } else { return Err(LimboError::InternalError( "RowId: cursor is not a table or virtual cursor".to_string(), )); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_seek_rowid( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SeekRowid { cursor_id, src_reg, target_pc, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); let rowid = match state.registers[*src_reg].get_owned_value() { OwnedValue::Integer(rowid) => Some(*rowid as u64), OwnedValue::Null => None, other => { return Err(LimboError::InternalError(format!( "SeekRowid: the value in the register is not an integer or NULL: {}", other ))); } }; match rowid { Some(rowid) => { let found = return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), SeekOp::EQ)); if !found { target_pc.to_offset_int() } else { state.pc + 1 } } None => target_pc.to_offset_int(), } }; state.pc = pc; Ok(InsnFunctionStepResult::Step) } pub fn op_deferred_seek( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::DeferredSeek { index_cursor_id, table_cursor_id, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.deferred_seek = Some((*index_cursor_id, *table_cursor_id)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_seek_ge( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SeekGE { cursor_id, start_reg, num_regs, target_pc, is_index, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); if *is_index { let found = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); 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::GE)); found }; if !found { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } else { let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); let rowid = match state.registers[*start_reg].get_owned_value() { OwnedValue::Null => { // All integer values are greater than null so we just rewind the cursor return_if_io!(cursor.rewind()); None } OwnedValue::Integer(rowid) => Some(*rowid as u64), _ => { return Err(LimboError::InternalError( "SeekGE: the value in the register is not an integer".into(), )); } }; match rowid { Some(rowid) => { let found = return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), SeekOp::GE)); if !found { target_pc.to_offset_int() } else { state.pc + 1 } } None => state.pc + 1, } }; state.pc = pc; } Ok(InsnFunctionStepResult::Step) } pub fn op_seek_gt( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SeekGT { cursor_id, start_reg, num_regs, target_pc, is_index, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); if *is_index { let found = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); 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)); found }; if !found { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } } else { let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); let rowid = match state.registers[*start_reg].get_owned_value() { OwnedValue::Null => { // All integer values are greater than null so we just rewind the cursor return_if_io!(cursor.rewind()); None } OwnedValue::Integer(rowid) => Some(*rowid as u64), _ => { return Err(LimboError::InternalError( "SeekGT: the value in the register is not an integer".into(), )); } }; let found = match rowid { Some(rowid) => { let found = return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), SeekOp::GT)); if !found { target_pc.to_offset_int() } else { state.pc + 1 } } None => state.pc + 1, }; found }; state.pc = pc; } Ok(InsnFunctionStepResult::Step) } pub fn op_idx_ge( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::IdxGE { cursor_id, start_reg, num_regs, target_pc, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); 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 let ord = idx_record.get_values()[..record_from_regs.len()] .partial_cmp(&record_from_regs.get_values()[..]) .unwrap(); if ord.is_ge() { target_pc.to_offset_int() } else { state.pc + 1 } } else { target_pc.to_offset_int() }; pc }; state.pc = pc; Ok(InsnFunctionStepResult::Step) } pub fn op_seek_end( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { if let Insn::SeekEnd { cursor_id } = *insn { let mut cursor = state.get_cursor(cursor_id); let cursor = cursor.as_btree_mut(); return_if_io!(cursor.seek_end()); } else { unreachable!("unexpected Insn {:?}", insn) } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_idx_le( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::IdxLE { cursor_id, start_reg, num_regs, target_pc, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); 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 let ord = idx_record.get_values()[..record_from_regs.len()] .partial_cmp(&record_from_regs.get_values()[..]) .unwrap(); if ord.is_le() { target_pc.to_offset_int() } else { state.pc + 1 } } else { target_pc.to_offset_int() }; pc }; state.pc = pc; Ok(InsnFunctionStepResult::Step) } pub fn op_idx_gt( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::IdxGT { cursor_id, start_reg, num_regs, target_pc, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); 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 let ord = idx_record.get_values()[..record_from_regs.len()] .partial_cmp(&record_from_regs.get_values()[..]) .unwrap(); if ord.is_gt() { target_pc.to_offset_int() } else { state.pc + 1 } } else { target_pc.to_offset_int() }; pc }; state.pc = pc; Ok(InsnFunctionStepResult::Step) } pub fn op_idx_lt( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::IdxLT { cursor_id, start_reg, num_regs, target_pc, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); let pc = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); 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 let ord = idx_record.get_values()[..record_from_regs.len()] .partial_cmp(&record_from_regs.get_values()[..]) .unwrap(); if ord.is_lt() { target_pc.to_offset_int() } else { state.pc + 1 } } else { target_pc.to_offset_int() }; pc }; state.pc = pc; Ok(InsnFunctionStepResult::Step) } pub fn op_decr_jump_zero( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::DecrJumpZero { reg, target_pc } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(target_pc.is_offset()); match state.registers[*reg].get_owned_value() { OwnedValue::Integer(n) => { let n = n - 1; if n == 0 { state.pc = target_pc.to_offset_int(); } else { state.registers[*reg] = Register::OwnedValue(OwnedValue::Integer(n)); state.pc += 1; } } _ => unreachable!("DecrJumpZero on non-integer register"), } Ok(InsnFunctionStepResult::Step) } pub fn op_agg_step( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::AggStep { acc_reg, col, delimiter, func, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let Register::OwnedValue(OwnedValue::Null) = state.registers[*acc_reg] { state.registers[*acc_reg] = match func { AggFunc::Avg => Register::Aggregate(AggContext::Avg( OwnedValue::Float(0.0), OwnedValue::Integer(0), )), AggFunc::Sum => Register::Aggregate(AggContext::Sum(OwnedValue::Null)), AggFunc::Total => { // The result of total() is always a floating point value. // No overflow error is ever raised if any prior input was a floating point value. // Total() never throws an integer overflow. Register::Aggregate(AggContext::Sum(OwnedValue::Float(0.0))) } AggFunc::Count | AggFunc::Count0 => { Register::Aggregate(AggContext::Count(OwnedValue::Integer(0))) } AggFunc::Max => { let col = state.registers[*col].get_owned_value(); match col { OwnedValue::Integer(_) => Register::Aggregate(AggContext::Max(None)), OwnedValue::Float(_) => Register::Aggregate(AggContext::Max(None)), OwnedValue::Text(_) => Register::Aggregate(AggContext::Max(None)), _ => { unreachable!(); } } } AggFunc::Min => { let col = state.registers[*col].get_owned_value(); match col { OwnedValue::Integer(_) => Register::Aggregate(AggContext::Min(None)), OwnedValue::Float(_) => Register::Aggregate(AggContext::Min(None)), OwnedValue::Text(_) => Register::Aggregate(AggContext::Min(None)), _ => { unreachable!(); } } } AggFunc::GroupConcat | AggFunc::StringAgg => { Register::Aggregate(AggContext::GroupConcat(OwnedValue::build_text(""))) } #[cfg(feature = "json")] AggFunc::JsonGroupArray | AggFunc::JsonbGroupArray => { Register::Aggregate(AggContext::GroupConcat(OwnedValue::Blob(vec![]))) } #[cfg(feature = "json")] AggFunc::JsonGroupObject | AggFunc::JsonbGroupObject => { Register::Aggregate(AggContext::GroupConcat(OwnedValue::Blob(vec![]))) } AggFunc::External(func) => match func.as_ref() { ExtFunc::Aggregate { init, step, finalize, argc, } => Register::Aggregate(AggContext::External(ExternalAggState { state: unsafe { (init)() }, argc: *argc, step_fn: *step, finalize_fn: *finalize, finalized_value: None, })), _ => unreachable!("scalar function called in aggregate context"), }, }; } match func { AggFunc::Avg => { let col = state.registers[*col].clone(); let Register::Aggregate(agg) = state.registers[*acc_reg].borrow_mut() else { unreachable!(); }; let AggContext::Avg(acc, count) = agg.borrow_mut() else { unreachable!(); }; *acc = exec_add(acc, col.get_owned_value()); *count += 1; } AggFunc::Sum | AggFunc::Total => { let col = state.registers[*col].clone(); let Register::Aggregate(agg) = state.registers[*acc_reg].borrow_mut() else { unreachable!(); }; let AggContext::Sum(acc) = agg.borrow_mut() else { unreachable!(); }; match col { Register::OwnedValue(owned_value) => { *acc += owned_value; } _ => unreachable!(), } } AggFunc::Count | AggFunc::Count0 => { let col = state.registers[*col].get_owned_value().clone(); if matches!( &state.registers[*acc_reg], Register::OwnedValue(OwnedValue::Null) ) { state.registers[*acc_reg] = Register::Aggregate(AggContext::Count(OwnedValue::Integer(0))); } let Register::Aggregate(agg) = state.registers[*acc_reg].borrow_mut() else { unreachable!(); }; let AggContext::Count(count) = agg.borrow_mut() else { unreachable!(); }; if !(matches!(func, AggFunc::Count) && matches!(col, OwnedValue::Null)) { *count += 1; }; } AggFunc::Max => { let col = state.registers[*col].clone(); let Register::Aggregate(agg) = state.registers[*acc_reg].borrow_mut() else { unreachable!(); }; let AggContext::Max(acc) = agg.borrow_mut() else { unreachable!(); }; match (acc.as_mut(), col.get_owned_value()) { (None, value) => { *acc = Some(value.clone()); } (Some(OwnedValue::Integer(ref mut current_max)), OwnedValue::Integer(value)) => { if *value > *current_max { *current_max = value.clone(); } } (Some(OwnedValue::Float(ref mut current_max)), OwnedValue::Float(value)) => { if *value > *current_max { *current_max = *value; } } (Some(OwnedValue::Text(ref mut current_max)), OwnedValue::Text(value)) => { if value.value > current_max.value { *current_max = value.clone(); } } _ => { eprintln!("Unexpected types in max aggregation"); } } } AggFunc::Min => { let col = state.registers[*col].clone(); let Register::Aggregate(agg) = state.registers[*acc_reg].borrow_mut() else { unreachable!(); }; let AggContext::Min(acc) = agg.borrow_mut() else { unreachable!(); }; match (acc.as_mut(), col.get_owned_value()) { (None, value) => { *acc.borrow_mut() = Some(value.clone()); } (Some(OwnedValue::Integer(ref mut current_min)), OwnedValue::Integer(value)) => { if *value < *current_min { *current_min = *value; } } (Some(OwnedValue::Float(ref mut current_min)), OwnedValue::Float(value)) => { if *value < *current_min { *current_min = *value; } } (Some(OwnedValue::Text(ref mut current_min)), OwnedValue::Text(text)) => { if text.value < current_min.value { *current_min = text.clone(); } } _ => { eprintln!("Unexpected types in min aggregation"); } } } AggFunc::GroupConcat | AggFunc::StringAgg => { let col = state.registers[*col].get_owned_value().clone(); let delimiter = state.registers[*delimiter].clone(); let Register::Aggregate(agg) = state.registers[*acc_reg].borrow_mut() else { unreachable!(); }; let AggContext::GroupConcat(acc) = agg.borrow_mut() else { unreachable!(); }; if acc.to_string().is_empty() { *acc = col; } else { match delimiter { Register::OwnedValue(owned_value) => { *acc += owned_value; } _ => unreachable!(), } *acc += col; } } #[cfg(feature = "json")] AggFunc::JsonGroupObject | AggFunc::JsonbGroupObject => { let key = state.registers[*col].clone(); let value = state.registers[*delimiter].clone(); let Register::Aggregate(agg) = state.registers[*acc_reg].borrow_mut() else { unreachable!(); }; let AggContext::GroupConcat(acc) = agg.borrow_mut() else { unreachable!(); }; let mut key_vec = convert_dbtype_to_raw_jsonb(&key.get_owned_value())?; let mut val_vec = convert_dbtype_to_raw_jsonb(&value.get_owned_value())?; match acc { OwnedValue::Blob(vec) => { if vec.is_empty() { // bits for obj header vec.push(12); vec.append(&mut key_vec); vec.append(&mut val_vec); } else { vec.append(&mut key_vec); vec.append(&mut val_vec); } } _ => unreachable!(), }; } #[cfg(feature = "json")] AggFunc::JsonGroupArray | AggFunc::JsonbGroupArray => { let col = state.registers[*col].clone(); let Register::Aggregate(agg) = state.registers[*acc_reg].borrow_mut() else { unreachable!(); }; let AggContext::GroupConcat(acc) = agg.borrow_mut() else { unreachable!(); }; let mut data = convert_dbtype_to_raw_jsonb(&col.get_owned_value())?; match acc { OwnedValue::Blob(vec) => { if vec.is_empty() { vec.push(11); vec.append(&mut data) } else { vec.append(&mut data); } } _ => unreachable!(), }; } AggFunc::External(_) => { let (step_fn, state_ptr, argc) = { let Register::Aggregate(agg) = &state.registers[*acc_reg] else { unreachable!(); }; let AggContext::External(agg_state) = agg else { unreachable!(); }; (agg_state.step_fn, agg_state.state, agg_state.argc) }; if argc == 0 { unsafe { step_fn(state_ptr, 0, std::ptr::null()) }; } else { let register_slice = &state.registers[*col..*col + argc]; let mut ext_values: Vec = Vec::with_capacity(argc); for ov in register_slice.iter() { ext_values.push(ov.get_owned_value().to_ffi()); } let argv_ptr = ext_values.as_ptr(); unsafe { step_fn(state_ptr, argc as i32, argv_ptr) }; for ext_value in ext_values { unsafe { ext_value.__free_internal_type() }; } } } }; state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_agg_final( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::AggFinal { register, func } = insn else { unreachable!("unexpected Insn {:?}", insn) }; match state.registers[*register].borrow_mut() { Register::Aggregate(agg) => match func { AggFunc::Avg => { let AggContext::Avg(acc, count) = agg.borrow_mut() else { unreachable!(); }; *acc /= count.clone(); state.registers[*register] = Register::OwnedValue(acc.clone()); } AggFunc::Sum | AggFunc::Total => { let AggContext::Sum(acc) = agg.borrow_mut() else { unreachable!(); }; let value = match acc { OwnedValue::Integer(i) => OwnedValue::Integer(*i), OwnedValue::Float(f) => OwnedValue::Float(*f), _ => OwnedValue::Float(0.0), }; state.registers[*register] = Register::OwnedValue(value); } AggFunc::Count | AggFunc::Count0 => { let AggContext::Count(count) = agg.borrow_mut() else { unreachable!(); }; state.registers[*register] = Register::OwnedValue(count.clone()); } AggFunc::Max => { let AggContext::Max(acc) = agg.borrow_mut() else { unreachable!(); }; match acc { Some(value) => state.registers[*register] = Register::OwnedValue(value.clone()), None => state.registers[*register] = Register::OwnedValue(OwnedValue::Null), } } AggFunc::Min => { let AggContext::Min(acc) = agg.borrow_mut() else { unreachable!(); }; match acc { Some(value) => state.registers[*register] = Register::OwnedValue(value.clone()), None => state.registers[*register] = Register::OwnedValue(OwnedValue::Null), } } AggFunc::GroupConcat | AggFunc::StringAgg => { let AggContext::GroupConcat(acc) = agg.borrow_mut() else { unreachable!(); }; state.registers[*register] = Register::OwnedValue(acc.clone()); } #[cfg(feature = "json")] AggFunc::JsonGroupObject => { let AggContext::GroupConcat(acc) = agg.borrow_mut() else { unreachable!(); }; let data = acc.to_blob().expect("Should be blob"); state.registers[*register] = Register::OwnedValue(json_from_raw_bytes_agg(data, false)?); } #[cfg(feature = "json")] AggFunc::JsonbGroupObject => { let AggContext::GroupConcat(acc) = agg.borrow_mut() else { unreachable!(); }; let data = acc.to_blob().expect("Should be blob"); state.registers[*register] = Register::OwnedValue(json_from_raw_bytes_agg(data, true)?); } #[cfg(feature = "json")] AggFunc::JsonGroupArray => { let AggContext::GroupConcat(acc) = agg.borrow_mut() else { unreachable!(); }; let data = acc.to_blob().expect("Should be blob"); state.registers[*register] = Register::OwnedValue(json_from_raw_bytes_agg(data, false)?); } #[cfg(feature = "json")] AggFunc::JsonbGroupArray => { let AggContext::GroupConcat(acc) = agg.borrow_mut() else { unreachable!(); }; let data = acc.to_blob().expect("Should be blob"); state.registers[*register] = Register::OwnedValue(json_from_raw_bytes_agg(data, true)?); } AggFunc::External(_) => { agg.compute_external()?; let AggContext::External(agg_state) = agg else { unreachable!(); }; match &agg_state.finalized_value { Some(value) => state.registers[*register] = Register::OwnedValue(value.clone()), None => state.registers[*register] = Register::OwnedValue(OwnedValue::Null), } } }, Register::OwnedValue(OwnedValue::Null) => { // when the set is empty match func { AggFunc::Total => { state.registers[*register] = Register::OwnedValue(OwnedValue::Float(0.0)); } AggFunc::Count | AggFunc::Count0 => { state.registers[*register] = Register::OwnedValue(OwnedValue::Integer(0)); } _ => {} } } _ => { unreachable!(); } }; state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_sorter_open( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SorterOpen { cursor_id, columns: _, order, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let order = order .get_values() .iter() .map(|v| match v { OwnedValue::Integer(i) => *i == 0, _ => unreachable!(), }) .collect(); let cursor = Sorter::new(order); let mut cursors = state.cursors.borrow_mut(); cursors .get_mut(*cursor_id) .unwrap() .replace(Cursor::new_sorter(cursor)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_sorter_data( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SorterData { cursor_id, dest_reg, pseudo_cursor, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let record = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_sorter_mut(); cursor.record().map(|r| r.clone()) }; let record = match record { Some(record) => record, None => { state.pc += 1; return Ok(InsnFunctionStepResult::Step); } }; state.registers[*dest_reg] = Register::Record(record.clone()); { let mut pseudo_cursor = state.get_cursor(*pseudo_cursor); pseudo_cursor.as_pseudo_mut().insert(record); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_sorter_insert( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SorterInsert { cursor_id, record_reg, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_sorter_mut(); let record = match &state.registers[*record_reg] { Register::Record(record) => record, _ => unreachable!("SorterInsert on non-record register"), }; cursor.insert(record); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_sorter_sort( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SorterSort { cursor_id, pc_if_empty, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let is_empty = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_sorter_mut(); let is_empty = cursor.is_empty(); if !is_empty { cursor.sort(); } is_empty }; if is_empty { state.pc = pc_if_empty.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_sorter_next( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SorterNext { cursor_id, pc_if_next, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(pc_if_next.is_offset()); let has_more = { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_sorter_mut(); cursor.next(); cursor.has_more() }; if has_more { state.pc = pc_if_next.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_function( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Function { constant_mask, func, start_reg, dest, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let arg_count = func.arg_count; match &func.func { #[cfg(feature = "json")] crate::function::Func::Json(json_func) => match json_func { JsonFunc::Json => { let json_value = &state.registers[*start_reg]; let json_str = get_json(json_value.get_owned_value(), None); match json_str { Ok(json) => state.registers[*dest] = Register::OwnedValue(json), Err(e) => return Err(e), } } JsonFunc::Jsonb => { let json_value = &state.registers[*start_reg]; let json_blob = jsonb(json_value.get_owned_value(), &state.json_cache); match json_blob { Ok(json) => state.registers[*dest] = Register::OwnedValue(json), Err(e) => return Err(e), } } JsonFunc::JsonArray | JsonFunc::JsonObject | JsonFunc::JsonbArray | JsonFunc::JsonbObject => { let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; let json_func = match json_func { JsonFunc::JsonArray => json_array, JsonFunc::JsonObject => json_object, JsonFunc::JsonbArray => jsonb_array, JsonFunc::JsonbObject => jsonb_object, _ => unreachable!(), }; let json_result = json_func(reg_values); match json_result { Ok(json) => state.registers[*dest] = Register::OwnedValue(json), Err(e) => return Err(e), } } JsonFunc::JsonExtract => { let result = match arg_count { 0 => Ok(OwnedValue::Null), _ => { let val = &state.registers[*start_reg]; let reg_values = &state.registers[*start_reg + 1..*start_reg + arg_count]; json_extract(val.get_owned_value(), reg_values, &state.json_cache) } }; match result { Ok(json) => state.registers[*dest] = Register::OwnedValue(json), Err(e) => return Err(e), } } JsonFunc::JsonbExtract => { let result = match arg_count { 0 => Ok(OwnedValue::Null), _ => { let val = &state.registers[*start_reg]; let reg_values = &state.registers[*start_reg + 1..*start_reg + arg_count]; jsonb_extract(val.get_owned_value(), reg_values, &state.json_cache) } }; match result { Ok(json) => state.registers[*dest] = Register::OwnedValue(json), Err(e) => return Err(e), } } JsonFunc::JsonArrowExtract | JsonFunc::JsonArrowShiftExtract => { assert_eq!(arg_count, 2); let json = &state.registers[*start_reg]; let path = &state.registers[*start_reg + 1]; let json_func = match json_func { JsonFunc::JsonArrowExtract => json_arrow_extract, JsonFunc::JsonArrowShiftExtract => json_arrow_shift_extract, _ => unreachable!(), }; let json_str = json_func( json.get_owned_value(), path.get_owned_value(), &state.json_cache, ); match json_str { Ok(json) => state.registers[*dest] = Register::OwnedValue(json), Err(e) => return Err(e), } } JsonFunc::JsonArrayLength | JsonFunc::JsonType => { let json_value = &state.registers[*start_reg]; let path_value = if arg_count > 1 { Some(&state.registers[*start_reg + 1]) } else { None }; let func_result = match json_func { JsonFunc::JsonArrayLength => json_array_length( json_value.get_owned_value(), path_value.map(|x| x.get_owned_value()), &state.json_cache, ), JsonFunc::JsonType => json_type( json_value.get_owned_value(), path_value.map(|x| x.get_owned_value()), ), _ => unreachable!(), }; match func_result { Ok(result) => state.registers[*dest] = Register::OwnedValue(result), Err(e) => return Err(e), } } JsonFunc::JsonErrorPosition => { let json_value = &state.registers[*start_reg]; match json_error_position(json_value.get_owned_value()) { Ok(pos) => state.registers[*dest] = Register::OwnedValue(pos), Err(e) => return Err(e), } } JsonFunc::JsonValid => { let json_value = &state.registers[*start_reg]; state.registers[*dest] = Register::OwnedValue(is_json_valid(json_value.get_owned_value())); } JsonFunc::JsonPatch => { assert_eq!(arg_count, 2); assert!(*start_reg + 1 < state.registers.len()); let target = &state.registers[*start_reg]; let patch = &state.registers[*start_reg + 1]; state.registers[*dest] = Register::OwnedValue(json_patch( target.get_owned_value(), patch.get_owned_value(), &state.json_cache, )?); } JsonFunc::JsonbPatch => { assert_eq!(arg_count, 2); assert!(*start_reg + 1 < state.registers.len()); let target = &state.registers[*start_reg]; let patch = &state.registers[*start_reg + 1]; state.registers[*dest] = Register::OwnedValue(jsonb_patch( target.get_owned_value(), patch.get_owned_value(), &state.json_cache, )?); } JsonFunc::JsonRemove => { if let Ok(json) = json_remove( &state.registers[*start_reg..*start_reg + arg_count], &state.json_cache, ) { state.registers[*dest] = Register::OwnedValue(json); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } JsonFunc::JsonbRemove => { if let Ok(json) = jsonb_remove( &state.registers[*start_reg..*start_reg + arg_count], &state.json_cache, ) { state.registers[*dest] = Register::OwnedValue(json); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } JsonFunc::JsonReplace => { if let Ok(json) = json_replace( &state.registers[*start_reg..*start_reg + arg_count], &state.json_cache, ) { state.registers[*dest] = Register::OwnedValue(json); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } JsonFunc::JsonbReplace => { if let Ok(json) = jsonb_replace( &state.registers[*start_reg..*start_reg + arg_count], &state.json_cache, ) { state.registers[*dest] = Register::OwnedValue(json); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } JsonFunc::JsonInsert => { if let Ok(json) = json_insert( &state.registers[*start_reg..*start_reg + arg_count], &state.json_cache, ) { state.registers[*dest] = Register::OwnedValue(json); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } JsonFunc::JsonbInsert => { if let Ok(json) = jsonb_insert( &state.registers[*start_reg..*start_reg + arg_count], &state.json_cache, ) { state.registers[*dest] = Register::OwnedValue(json); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } JsonFunc::JsonPretty => { let json_value = &state.registers[*start_reg]; let indent = if arg_count > 1 { Some(&state.registers[*start_reg + 1]) } else { None }; // Blob should be converted to Ascii in a lossy way // However, Rust strings uses utf-8 // so the behavior at the moment is slightly different // To the way blobs are parsed here in SQLite. let indent = match indent { Some(value) => match value.get_owned_value() { OwnedValue::Text(text) => text.as_str(), OwnedValue::Integer(val) => &val.to_string(), OwnedValue::Float(val) => &val.to_string(), OwnedValue::Blob(val) => &String::from_utf8_lossy(val), _ => " ", }, // If the second argument is omitted or is NULL, then indentation is four spaces per level None => " ", }; let json_str = get_json(json_value.get_owned_value(), Some(indent))?; state.registers[*dest] = Register::OwnedValue(json_str); } JsonFunc::JsonSet => { if arg_count % 2 == 0 { bail_constraint_error!("json_set() needs an odd number of arguments") } let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; let json_result = json_set(reg_values, &state.json_cache); match json_result { Ok(json) => state.registers[*dest] = Register::OwnedValue(json), Err(e) => return Err(e), } } JsonFunc::JsonbSet => { if arg_count % 2 == 0 { bail_constraint_error!("json_set() needs an odd number of arguments") } let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; let json_result = jsonb_set(reg_values, &state.json_cache); match json_result { Ok(json) => state.registers[*dest] = Register::OwnedValue(json), Err(e) => return Err(e), } } JsonFunc::JsonQuote => { let json_value = &state.registers[*start_reg]; match json_quote(json_value.get_owned_value()) { Ok(result) => state.registers[*dest] = Register::OwnedValue(result), Err(e) => return Err(e), } } }, crate::function::Func::Scalar(scalar_func) => match scalar_func { ScalarFunc::Cast => { assert_eq!(arg_count, 2); assert!(*start_reg + 1 < state.registers.len()); let reg_value_argument = state.registers[*start_reg].clone(); let OwnedValue::Text(reg_value_type) = state.registers[*start_reg + 1].get_owned_value().clone() else { unreachable!("Cast with non-text type"); }; let result = exec_cast( ®_value_argument.get_owned_value(), reg_value_type.as_str(), ); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Changes => { let res = &program.connection.upgrade().unwrap().last_change; let changes = res.get(); state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(changes)); } ScalarFunc::Char => { let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; state.registers[*dest] = Register::OwnedValue(exec_char(reg_values)); } ScalarFunc::Coalesce => {} ScalarFunc::Concat => { let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; let result = exec_concat_strings(reg_values); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::ConcatWs => { let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; let result = exec_concat_ws(reg_values); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Glob => { let pattern = &state.registers[*start_reg]; let text = &state.registers[*start_reg + 1]; let result = match (pattern.get_owned_value(), text.get_owned_value()) { (OwnedValue::Text(pattern), OwnedValue::Text(text)) => { let cache = if *constant_mask > 0 { Some(&mut state.regex_cache.glob) } else { None }; OwnedValue::Integer(exec_glob(cache, pattern.as_str(), text.as_str()) as i64) } _ => { unreachable!("Like on non-text registers"); } }; state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::IfNull => {} ScalarFunc::Iif => {} ScalarFunc::Instr => { let reg_value = &state.registers[*start_reg]; let pattern_value = &state.registers[*start_reg + 1]; let result = exec_instr(reg_value.get_owned_value(), pattern_value.get_owned_value()); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::LastInsertRowid => { if let Some(conn) = program.connection.upgrade() { state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(conn.last_insert_rowid() as i64)); } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); } } ScalarFunc::Like => { let pattern = &state.registers[*start_reg]; let match_expression = &state.registers[*start_reg + 1]; let pattern = match pattern.get_owned_value() { OwnedValue::Text(_) => pattern.get_owned_value(), _ => &exec_cast(pattern.get_owned_value(), "TEXT"), }; let match_expression = match match_expression.get_owned_value() { OwnedValue::Text(_) => match_expression.get_owned_value(), _ => &exec_cast(match_expression.get_owned_value(), "TEXT"), }; let result = match (pattern, match_expression) { (OwnedValue::Text(pattern), OwnedValue::Text(match_expression)) if arg_count == 3 => { let escape = match construct_like_escape_arg( state.registers[*start_reg + 2].get_owned_value(), ) { Ok(x) => x, Err(e) => return Err(e), }; OwnedValue::Integer(exec_like_with_escape( pattern.as_str(), match_expression.as_str(), escape, ) as i64) } (OwnedValue::Text(pattern), OwnedValue::Text(match_expression)) => { let cache = if *constant_mask > 0 { Some(&mut state.regex_cache.like) } else { None }; OwnedValue::Integer(exec_like( cache, pattern.as_str(), match_expression.as_str(), ) as i64) } (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, _ => { unreachable!("Like failed"); } }; state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Abs | ScalarFunc::Lower | ScalarFunc::Upper | ScalarFunc::Length | ScalarFunc::OctetLength | ScalarFunc::Typeof | ScalarFunc::Unicode | ScalarFunc::Quote | ScalarFunc::RandomBlob | ScalarFunc::Sign | ScalarFunc::Soundex | ScalarFunc::ZeroBlob => { let reg_value = state.registers[*start_reg].borrow_mut().get_owned_value(); let result = match scalar_func { ScalarFunc::Sign => exec_sign(reg_value), ScalarFunc::Abs => Some(exec_abs(reg_value)?), ScalarFunc::Lower => exec_lower(reg_value), ScalarFunc::Upper => exec_upper(reg_value), ScalarFunc::Length => Some(exec_length(reg_value)), ScalarFunc::OctetLength => Some(exec_octet_length(reg_value)), ScalarFunc::Typeof => Some(exec_typeof(reg_value)), ScalarFunc::Unicode => Some(exec_unicode(reg_value)), ScalarFunc::Quote => Some(exec_quote(reg_value)), ScalarFunc::RandomBlob => Some(exec_randomblob(reg_value)), ScalarFunc::ZeroBlob => Some(exec_zeroblob(reg_value)), ScalarFunc::Soundex => Some(exec_soundex(reg_value)), _ => unreachable!(), }; state.registers[*dest] = Register::OwnedValue(result.unwrap_or(OwnedValue::Null)); } ScalarFunc::Hex => { let reg_value = state.registers[*start_reg].borrow_mut(); let result = exec_hex(reg_value.get_owned_value()); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Unhex => { let reg_value = &state.registers[*start_reg]; let ignored_chars = state.registers.get(*start_reg + 1); let result = exec_unhex( reg_value.get_owned_value(), ignored_chars.map(|x| x.get_owned_value()), ); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Random => { state.registers[*dest] = Register::OwnedValue(exec_random()); } ScalarFunc::Trim => { let reg_value = &state.registers[*start_reg]; let pattern_value = if func.arg_count == 2 { state.registers.get(*start_reg + 1) } else { None }; let result = exec_trim( reg_value.get_owned_value(), pattern_value.map(|x| x.get_owned_value()), ); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::LTrim => { let reg_value = &state.registers[*start_reg]; let pattern_value = if func.arg_count == 2 { state.registers.get(*start_reg + 1) } else { None }; let result = exec_ltrim( reg_value.get_owned_value(), pattern_value.map(|x| x.get_owned_value()), ); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::RTrim => { let reg_value = &state.registers[*start_reg]; let pattern_value = if func.arg_count == 2 { state.registers.get(*start_reg + 1) } else { None }; let result = exec_rtrim( ®_value.get_owned_value(), pattern_value.map(|x| x.get_owned_value()), ); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Round => { let reg_value = &state.registers[*start_reg]; assert!(arg_count == 1 || arg_count == 2); let precision_value = if arg_count > 1 { state.registers.get(*start_reg + 1) } else { None }; let result = exec_round( reg_value.get_owned_value(), precision_value.map(|x| x.get_owned_value()), ); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Min => { let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; state.registers[*dest] = Register::OwnedValue(exec_min(reg_values)); } ScalarFunc::Max => { let reg_values = &state.registers[*start_reg..*start_reg + arg_count]; state.registers[*dest] = Register::OwnedValue(exec_max(reg_values)); } ScalarFunc::Nullif => { let first_value = &state.registers[*start_reg]; let second_value = &state.registers[*start_reg + 1]; state.registers[*dest] = Register::OwnedValue(exec_nullif( first_value.get_owned_value(), second_value.get_owned_value(), )); } ScalarFunc::Substr | ScalarFunc::Substring => { let str_value = &state.registers[*start_reg]; let start_value = &state.registers[*start_reg + 1]; let length_value = if func.arg_count == 3 { Some(&state.registers[*start_reg + 2]) } else { None }; let result = exec_substring( str_value.get_owned_value(), start_value.get_owned_value(), length_value.map(|x| x.get_owned_value()), ); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Date => { let result = exec_date(&state.registers[*start_reg..*start_reg + arg_count]); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Time => { let values = &state.registers[*start_reg..*start_reg + arg_count]; let result = exec_time(values); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::TotalChanges => { let res = &program.connection.upgrade().unwrap().total_changes; let total_changes = res.get(); state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(total_changes)); } ScalarFunc::DateTime => { let result = exec_datetime_full(&state.registers[*start_reg..*start_reg + arg_count]); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::JulianDay => { if *start_reg == 0 { let julianday: String = exec_julianday(&OwnedValue::build_text("now"))?; state.registers[*dest] = Register::OwnedValue(OwnedValue::build_text(&julianday)); } else { let datetime_value = &state.registers[*start_reg]; let julianday = exec_julianday(datetime_value.get_owned_value()); match julianday { Ok(time) => { state.registers[*dest] = Register::OwnedValue(OwnedValue::build_text(&time)) } Err(e) => { return Err(LimboError::ParseError(format!( "Error encountered while parsing datetime value: {}", e ))); } } } } ScalarFunc::UnixEpoch => { if *start_reg == 0 { let unixepoch: String = exec_unixepoch(&OwnedValue::build_text("now"))?; state.registers[*dest] = Register::OwnedValue(OwnedValue::build_text(&unixepoch)); } else { let datetime_value = &state.registers[*start_reg]; let unixepoch = exec_unixepoch(datetime_value.get_owned_value()); match unixepoch { Ok(time) => { state.registers[*dest] = Register::OwnedValue(OwnedValue::build_text(&time)) } Err(e) => { return Err(LimboError::ParseError(format!( "Error encountered while parsing datetime value: {}", e ))); } } } } ScalarFunc::SqliteVersion => { let version_integer: i64 = DATABASE_VERSION.get().unwrap().parse()?; let version = execute_sqlite_version(version_integer); state.registers[*dest] = Register::OwnedValue(OwnedValue::build_text(&version)); } ScalarFunc::SqliteSourceId => { let src_id = format!( "{} {}", info::build::BUILT_TIME_SQLITE, info::build::GIT_COMMIT_HASH.unwrap_or("unknown") ); state.registers[*dest] = Register::OwnedValue(OwnedValue::build_text(&src_id)); } ScalarFunc::Replace => { assert_eq!(arg_count, 3); let source = &state.registers[*start_reg]; let pattern = &state.registers[*start_reg + 1]; let replacement = &state.registers[*start_reg + 2]; state.registers[*dest] = Register::OwnedValue(exec_replace( source.get_owned_value(), pattern.get_owned_value(), replacement.get_owned_value(), )); } #[cfg(feature = "fs")] ScalarFunc::LoadExtension => { let extension = &state.registers[*start_reg]; let ext = resolve_ext_path(&extension.get_owned_value().to_string())?; if let Some(conn) = program.connection.upgrade() { conn.load_extension(ext)?; } } ScalarFunc::StrfTime => { let result = exec_strftime(&state.registers[*start_reg..*start_reg + arg_count]); state.registers[*dest] = Register::OwnedValue(result); } ScalarFunc::Printf => { let result = exec_printf(&state.registers[*start_reg..*start_reg + arg_count])?; state.registers[*dest] = Register::OwnedValue(result); } }, crate::function::Func::Vector(vector_func) => match vector_func { VectorFunc::Vector => { let result = vector32(&state.registers[*start_reg..*start_reg + arg_count])?; state.registers[*dest] = Register::OwnedValue(result); } VectorFunc::Vector32 => { let result = vector32(&state.registers[*start_reg..*start_reg + arg_count])?; state.registers[*dest] = Register::OwnedValue(result); } VectorFunc::Vector64 => { let result = vector64(&state.registers[*start_reg..*start_reg + arg_count])?; state.registers[*dest] = Register::OwnedValue(result); } VectorFunc::VectorExtract => { let result = vector_extract(&state.registers[*start_reg..*start_reg + arg_count])?; state.registers[*dest] = Register::OwnedValue(result); } VectorFunc::VectorDistanceCos => { let result = vector_distance_cos(&state.registers[*start_reg..*start_reg + arg_count])?; state.registers[*dest] = Register::OwnedValue(result); } }, crate::function::Func::External(f) => match f.func { ExtFunc::Scalar(f) => { if arg_count == 0 { let result_c_value: ExtValue = unsafe { (f)(0, std::ptr::null()) }; match OwnedValue::from_ffi(result_c_value) { Ok(result_ov) => { state.registers[*dest] = Register::OwnedValue(result_ov); } Err(e) => { return Err(e); } } } else { let register_slice = &state.registers[*start_reg..*start_reg + arg_count]; let mut ext_values: Vec = Vec::with_capacity(arg_count); for ov in register_slice.iter() { let val = ov.get_owned_value().to_ffi(); ext_values.push(val); } let argv_ptr = ext_values.as_ptr(); let result_c_value: ExtValue = unsafe { (f)(arg_count as i32, argv_ptr) }; match OwnedValue::from_ffi(result_c_value) { Ok(result_ov) => { state.registers[*dest] = Register::OwnedValue(result_ov); } Err(e) => { return Err(e); } } } } _ => unreachable!("aggregate called in scalar context"), }, crate::function::Func::Math(math_func) => match math_func.arity() { MathFuncArity::Nullary => match math_func { MathFunc::Pi => { state.registers[*dest] = Register::OwnedValue(OwnedValue::Float(std::f64::consts::PI)); } _ => { unreachable!("Unexpected mathematical Nullary function {:?}", math_func); } }, MathFuncArity::Unary => { let reg_value = &state.registers[*start_reg]; let result = exec_math_unary(reg_value.get_owned_value(), math_func); state.registers[*dest] = Register::OwnedValue(result); } MathFuncArity::Binary => { let lhs = &state.registers[*start_reg]; let rhs = &state.registers[*start_reg + 1]; let result = exec_math_binary(lhs.get_owned_value(), rhs.get_owned_value(), math_func); state.registers[*dest] = Register::OwnedValue(result); } MathFuncArity::UnaryOrBinary => match math_func { MathFunc::Log => { let result = match arg_count { 1 => { let arg = &state.registers[*start_reg]; exec_math_log(arg.get_owned_value(), None) } 2 => { let base = &state.registers[*start_reg]; let arg = &state.registers[*start_reg + 1]; exec_math_log(arg.get_owned_value(), Some(base.get_owned_value())) } _ => unreachable!( "{:?} function with unexpected number of arguments", math_func ), }; state.registers[*dest] = Register::OwnedValue(result); } _ => unreachable!( "Unexpected mathematical UnaryOrBinary function {:?}", math_func ), }, }, crate::function::Func::Agg(_) => { unreachable!("Aggregate functions should not be handled here") } } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_init_coroutine( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::InitCoroutine { yield_reg, jump_on_definition, start_offset, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; assert!(jump_on_definition.is_offset()); let start_offset = start_offset.to_offset_int(); state.registers[*yield_reg] = Register::OwnedValue(OwnedValue::Integer(start_offset as i64)); state.ended_coroutine.unset(*yield_reg); let jump_on_definition = jump_on_definition.to_offset_int(); state.pc = if jump_on_definition == 0 { state.pc + 1 } else { jump_on_definition }; Ok(InsnFunctionStepResult::Step) } pub fn op_end_coroutine( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::EndCoroutine { yield_reg } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let OwnedValue::Integer(pc) = state.registers[*yield_reg].get_owned_value() { state.ended_coroutine.set(*yield_reg); let pc: u32 = (*pc) .try_into() .unwrap_or_else(|_| panic!("EndCoroutine: pc overflow: {}", pc)); state.pc = pc - 1; // yield jump is always next to yield. Here we subtract 1 to go back to yield instruction } else { unreachable!(); } Ok(InsnFunctionStepResult::Step) } pub fn op_yield( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Yield { yield_reg, end_offset, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if let OwnedValue::Integer(pc) = state.registers[*yield_reg].get_owned_value() { if state.ended_coroutine.get(*yield_reg) { state.pc = end_offset.to_offset_int(); } else { let pc: u32 = (*pc) .try_into() .unwrap_or_else(|_| panic!("Yield: pc overflow: {}", pc)); // swap the program counter with the value in the yield register // this is the mechanism that allows jumping back and forth between the coroutine and the caller (state.pc, state.registers[*yield_reg]) = ( pc, Register::OwnedValue(OwnedValue::Integer((state.pc + 1) as i64)), ); } } else { unreachable!( "yield_reg {} contains non-integer value: {:?}", *yield_reg, state.registers[*yield_reg] ); } Ok(InsnFunctionStepResult::Step) } pub fn op_insert_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::InsertAsync { cursor, key_reg, record_reg, flag: _, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = state.get_cursor(*cursor); let cursor = cursor.as_btree_mut(); let record = match &state.registers[*record_reg] { Register::Record(r) => r, _ => unreachable!("Not a record! Cannot insert a non record value."), }; let key = match &state.registers[*key_reg].get_owned_value() { OwnedValue::Integer(i) => *i, _ => unreachable!("expected integer key"), }; // NOTE(pere): Sending moved_before == true is okay because we moved before but // if we were to set to false after starting a balance procedure, it might // leave undefined state. return_if_io!(cursor.insert(&BTreeKey::new_table_rowid(key as u64, Some(record)), true)); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_insert_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::InsertAwait { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); cursor.wait_for_completion()?; // Only update last_insert_rowid for regular table inserts, not schema modifications if cursor.root_page() != 1 { if let Some(rowid) = cursor.rowid()? { if let Some(conn) = program.connection.upgrade() { conn.update_last_rowid(rowid); } let prev_changes = program.n_change.get(); program.n_change.set(prev_changes + 1); } } } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_delete_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::DeleteAsync { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); return_if_io!(cursor.delete()); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_idx_insert_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { if let Insn::IdxInsertAsync { cursor_id, record_reg, flags, .. } = *insn { let (_, cursor_type) = program.cursor_ref.get(cursor_id).unwrap(); let CursorType::BTreeIndex(index_meta) = cursor_type else { panic!("IdxInsert: not a BTree index cursor"); }; { let mut cursor = state.get_cursor(cursor_id); let cursor = cursor.as_btree_mut(); let record = match &state.registers[record_reg] { Register::Record(ref r) => r, _ => return Err(LimboError::InternalError("expected record".into())), }; let moved_before = if index_meta.unique { // check for uniqueness violation match cursor.key_exists_in_index(record)? { CursorResult::Ok(true) => { return Err(LimboError::Constraint( "UNIQUE constraint failed: duplicate key".into(), )) } CursorResult::IO => return Ok(InsnFunctionStepResult::IO), CursorResult::Ok(false) => {} }; false } else { flags.has(IdxInsertFlags::USE_SEEK) }; // insert record as key return_if_io!(cursor.insert(&BTreeKey::new_index_key(record), moved_before)); } state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_idx_insert_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { if let Insn::IdxInsertAwait { cursor_id } = *insn { { let mut cursor = state.get_cursor(cursor_id); let cursor = cursor.as_btree_mut(); cursor.wait_for_completion()?; } // TODO: flag optimizations, update n_change if OPFLAG_NCHANGE state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_delete_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::DeleteAwait { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; { let mut cursor = state.get_cursor(*cursor_id); let cursor = cursor.as_btree_mut(); cursor.wait_for_completion()?; } let prev_changes = program.n_change.get(); program.n_change.set(prev_changes + 1); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_new_rowid( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::NewRowid { cursor, rowid_reg, .. } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let rowid = { let mut cursor = state.get_cursor(*cursor); let cursor = cursor.as_btree_mut(); // TODO: make io handle rng let rowid = return_if_io!(get_new_rowid(cursor, thread_rng())); rowid }; state.registers[*rowid_reg] = Register::OwnedValue(OwnedValue::Integer(rowid)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_must_be_int( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::MustBeInt { reg } = insn else { unreachable!("unexpected Insn {:?}", insn) }; match &state.registers[*reg].get_owned_value() { OwnedValue::Integer(_) => {} OwnedValue::Float(f) => match cast_real_to_integer(*f) { Ok(i) => state.registers[*reg] = Register::OwnedValue(OwnedValue::Integer(i)), Err(_) => crate::bail_parse_error!( "MustBeInt: the value in register cannot be cast to integer" ), }, OwnedValue::Text(text) => match checked_cast_text_to_numeric(text.as_str()) { Ok(OwnedValue::Integer(i)) => { state.registers[*reg] = Register::OwnedValue(OwnedValue::Integer(i)) } Ok(OwnedValue::Float(f)) => { state.registers[*reg] = Register::OwnedValue(OwnedValue::Integer(f as i64)) } _ => crate::bail_parse_error!( "MustBeInt: the value in register cannot be cast to integer" ), }, _ => { crate::bail_parse_error!("MustBeInt: the value in register cannot be cast to integer"); } }; state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_soft_null( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::SoftNull { reg } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*reg] = Register::OwnedValue(OwnedValue::Null); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_not_exists( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::NotExists { cursor, rowid_reg, target_pc, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let exists = { let mut cursor = must_be_btree_cursor!(*cursor, program.cursor_ref, state, "NotExists"); let cursor = cursor.as_btree_mut(); let exists = return_if_io!(cursor.exists(state.registers[*rowid_reg].get_owned_value())); exists }; if exists { state.pc += 1; } else { state.pc = target_pc.to_offset_int(); } Ok(InsnFunctionStepResult::Step) } pub fn op_offset_limit( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::OffsetLimit { limit_reg, combined_reg, offset_reg, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let limit_val = match state.registers[*limit_reg].get_owned_value() { OwnedValue::Integer(val) => val, _ => { return Err(LimboError::InternalError( "OffsetLimit: the value in limit_reg is not an integer".into(), )); } }; let offset_val = match state.registers[*offset_reg].get_owned_value() { OwnedValue::Integer(val) if *val < 0 => 0, OwnedValue::Integer(val) if *val >= 0 => *val, _ => { return Err(LimboError::InternalError( "OffsetLimit: the value in offset_reg is not an integer".into(), )); } }; let offset_limit_sum = limit_val.overflowing_add(offset_val); if *limit_val <= 0 || offset_limit_sum.1 { state.registers[*combined_reg] = Register::OwnedValue(OwnedValue::Integer(-1)); } else { state.registers[*combined_reg] = Register::OwnedValue(OwnedValue::Integer(offset_limit_sum.0)); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } // this cursor may be reused for next insert // Update: tablemoveto is used to travers on not exists, on insert depending on flags if nonseek it traverses again. // If not there might be some optimizations obviously. pub fn op_open_write_async( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::OpenWriteAsync { cursor_id, root_page, .. } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let root_page = match root_page { RegisterOrLiteral::Literal(lit) => *lit as u64, RegisterOrLiteral::Register(reg) => match &state.registers[*reg].get_owned_value() { OwnedValue::Integer(val) => *val as u64, _ => { return Err(LimboError::InternalError( "OpenWriteAsync: the value in root_page is not an integer".into(), )); } }, }; let (_, cursor_type) = program.cursor_ref.get(*cursor_id).unwrap(); let mut cursors = state.cursors.borrow_mut(); let is_index = cursor_type.is_index(); let mv_cursor = match state.mv_tx_id { Some(tx_id) => { let table_id = root_page; let mv_store = mv_store.unwrap().clone(); let mv_cursor = Rc::new(RefCell::new( MvCursor::new(mv_store.clone(), tx_id, table_id).unwrap(), )); Some(mv_cursor) } None => None, }; let cursor = BTreeCursor::new(mv_cursor, pager.clone(), root_page as usize); if is_index { cursors .get_mut(*cursor_id) .unwrap() .replace(Cursor::new_btree(cursor)); } else { cursors .get_mut(*cursor_id) .unwrap() .replace(Cursor::new_btree(cursor)); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_open_write_await( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::OpenWriteAwait {} = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_copy( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Copy { src_reg, dst_reg, amount, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; for i in 0..=*amount { state.registers[*dst_reg + i] = state.registers[*src_reg + i].clone(); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_create_btree( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::CreateBtree { db, root, flags } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if *db > 0 { // TODO: implement temp databases todo!("temp databases not implemented yet"); } let root_page = pager.btree_create(*flags); state.registers[*root] = Register::OwnedValue(OwnedValue::Integer(root_page as i64)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_destroy( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Destroy { root, former_root_reg: _, is_temp, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if *is_temp == 1 { todo!("temp databases not implemented yet."); } let mut cursor = BTreeCursor::new(None, pager.clone(), *root); cursor.btree_destroy()?; state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_drop_table( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::DropTable { db, _p2, _p3, table_name, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if *db > 0 { todo!("temp databases not implemented yet"); } if let Some(conn) = program.connection.upgrade() { let mut schema = conn.schema.write(); schema.remove_indices_for_table(table_name); schema.remove_table(table_name); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_close( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Close { cursor_id } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let mut cursors = state.cursors.borrow_mut(); cursors.get_mut(*cursor_id).unwrap().take(); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_is_null( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::IsNull { reg, target_pc } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if matches!( state.registers[*reg], Register::OwnedValue(OwnedValue::Null) ) { state.pc = target_pc.to_offset_int(); } else { state.pc += 1; } Ok(InsnFunctionStepResult::Step) } pub fn op_page_count( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::PageCount { db, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if *db > 0 { // TODO: implement temp databases todo!("temp databases not implemented yet"); } // SQLite returns "0" on an empty database, and 2 on the first insertion, // so we'll mimic that behavior. let mut pages = pager.db_header.lock().database_size.into(); if pages == 1 { pages = 0; } state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(pages)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_parse_schema( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::ParseSchema { db: _, where_clause, } = insn else { unreachable!("unexpected Insn {:?}", insn) }; let conn = program.connection.upgrade(); let conn = conn.as_ref().unwrap(); let stmt = conn.prepare(format!( "SELECT * FROM sqlite_schema WHERE {}", where_clause ))?; let mut schema = conn.schema.write(); // TODO: This function below is synchronous, make it async parse_schema_rows( Some(stmt), &mut schema, conn.pager.io.clone(), &conn.syms.borrow(), state.mv_tx_id, )?; state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_read_cookie( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::ReadCookie { db, dest, cookie } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if *db > 0 { // TODO: implement temp databases todo!("temp databases not implemented yet"); } let cookie_value = match cookie { Cookie::UserVersion => pager.db_header.lock().user_version.into(), cookie => todo!("{cookie:?} is not yet implement for ReadCookie"), }; state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(cookie_value)); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_shift_right( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::ShiftRight { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_shift_right( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_shift_left( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::ShiftLeft { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_shift_left( state.registers[*lhs].get_owned_value(), state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_variable( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Variable { index, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue( state .get_parameter(*index) .ok_or(LimboError::Unbound(*index))? .clone(), ); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_zero_or_null( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::ZeroOrNull { rg1, rg2, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; if *state.registers[*rg1].get_owned_value() == OwnedValue::Null || *state.registers[*rg2].get_owned_value() == OwnedValue::Null { state.registers[*dest] = Register::OwnedValue(OwnedValue::Null) } else { state.registers[*dest] = Register::OwnedValue(OwnedValue::Integer(0)); } state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_not( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Not { reg, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_boolean_not(state.registers[*reg].get_owned_value())); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_concat( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Concat { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_concat( &state.registers[*lhs].get_owned_value(), &state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_and( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::And { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_and( &state.registers[*lhs].get_owned_value(), &state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_or( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { let Insn::Or { lhs, rhs, dest } = insn else { unreachable!("unexpected Insn {:?}", insn) }; state.registers[*dest] = Register::OwnedValue(exec_or( &state.registers[*lhs].get_owned_value(), &state.registers[*rhs].get_owned_value(), )); state.pc += 1; Ok(InsnFunctionStepResult::Step) } pub fn op_noop( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { // Do nothing // Advance the program counter for the next opcode state.pc += 1; Ok(InsnFunctionStepResult::Step) } fn exec_lower(reg: &OwnedValue) -> Option { match reg { OwnedValue::Text(t) => Some(OwnedValue::build_text(&t.as_str().to_lowercase())), t => Some(t.to_owned()), } } fn exec_length(reg: &OwnedValue) -> OwnedValue { match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { OwnedValue::Integer(reg.to_string().chars().count() as i64) } OwnedValue::Blob(blob) => OwnedValue::Integer(blob.len() as i64), _ => reg.to_owned(), } } fn exec_octet_length(reg: &OwnedValue) -> OwnedValue { match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { OwnedValue::Integer(reg.to_string().into_bytes().len() as i64) } OwnedValue::Blob(blob) => OwnedValue::Integer(blob.len() as i64), _ => reg.to_owned(), } } fn exec_upper(reg: &OwnedValue) -> Option { match reg { OwnedValue::Text(t) => Some(OwnedValue::build_text(&t.as_str().to_uppercase())), t => Some(t.to_owned()), } } fn exec_concat_strings(registers: &[Register]) -> OwnedValue { let mut result = String::new(); for reg in registers { match reg.get_owned_value() { OwnedValue::Null => continue, OwnedValue::Blob(_) => todo!("TODO concat blob"), v => result.push_str(&format!("{}", v)), } } OwnedValue::build_text(&result) } fn exec_concat_ws(registers: &[Register]) -> OwnedValue { if registers.is_empty() { return OwnedValue::Null; } let separator = match ®isters[0].get_owned_value() { OwnedValue::Null | OwnedValue::Blob(_) => return OwnedValue::Null, v => format!("{}", v), }; let mut result = String::new(); for (i, reg) in registers.iter().enumerate().skip(1) { if i > 1 { result.push_str(&separator); } match reg.get_owned_value() { v if matches!( v, OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) ) => { result.push_str(&format!("{}", v)) } _ => continue, } } OwnedValue::build_text(&result) } fn exec_sign(reg: &OwnedValue) -> Option { let num = match reg { OwnedValue::Integer(i) => *i as f64, OwnedValue::Float(f) => *f, OwnedValue::Text(s) => { if let Ok(i) = s.as_str().parse::() { i as f64 } else if let Ok(f) = s.as_str().parse::() { f } else { return Some(OwnedValue::Null); } } OwnedValue::Blob(b) => match std::str::from_utf8(b) { Ok(s) => { if let Ok(i) = s.parse::() { i as f64 } else if let Ok(f) = s.parse::() { f } else { return Some(OwnedValue::Null); } } Err(_) => return Some(OwnedValue::Null), }, _ => return Some(OwnedValue::Null), }; let sign = if num > 0.0 { 1 } else if num < 0.0 { -1 } else { 0 }; Some(OwnedValue::Integer(sign)) } /// Generates the Soundex code for a given word pub fn exec_soundex(reg: &OwnedValue) -> OwnedValue { let s = match reg { OwnedValue::Null => return OwnedValue::build_text("?000"), OwnedValue::Text(s) => { // return ?000 if non ASCII alphabet character is found if !s.as_str().chars().all(|c| c.is_ascii_alphabetic()) { return OwnedValue::build_text("?000"); } s.clone() } _ => return OwnedValue::build_text("?000"), // For unsupported types, return NULL }; // Remove numbers and spaces let word: String = s .as_str() .chars() .filter(|c| !c.is_ascii_digit()) .collect::() .replace(" ", ""); if word.is_empty() { return OwnedValue::build_text("0000"); } let soundex_code = |c| match c { 'b' | 'f' | 'p' | 'v' => Some('1'), 'c' | 'g' | 'j' | 'k' | 'q' | 's' | 'x' | 'z' => Some('2'), 'd' | 't' => Some('3'), 'l' => Some('4'), 'm' | 'n' => Some('5'), 'r' => Some('6'), _ => None, }; // Convert the word to lowercase for consistent lookups let word = word.to_lowercase(); let first_letter = word.chars().next().unwrap(); // Remove all occurrences of 'h' and 'w' except the first letter let code: String = word .chars() .skip(1) .filter(|&ch| ch != 'h' && ch != 'w') .fold(first_letter.to_string(), |mut acc, ch| { acc.push(ch); acc }); // Replace consonants with digits based on Soundex mapping let tmp: String = code .chars() .map(|ch| match soundex_code(ch) { Some(code) => code.to_string(), None => ch.to_string(), }) .collect(); // Remove adjacent same digits let tmp = tmp.chars().fold(String::new(), |mut acc, ch| { if !acc.ends_with(ch) { acc.push(ch); } acc }); // Remove all occurrences of a, e, i, o, u, y except the first letter let mut result = tmp .chars() .enumerate() .filter(|(i, ch)| *i == 0 || !matches!(ch, 'a' | 'e' | 'i' | 'o' | 'u' | 'y')) .map(|(_, ch)| ch) .collect::(); // If the first symbol is a digit, replace it with the saved first letter if let Some(first_digit) = result.chars().next() { if first_digit.is_ascii_digit() { result.replace_range(0..1, &first_letter.to_string()); } } // Append zeros if the result contains less than 4 characters while result.len() < 4 { result.push('0'); } // Retain the first 4 characters and convert to uppercase result.truncate(4); OwnedValue::build_text(&result.to_uppercase()) } fn exec_abs(reg: &OwnedValue) -> Result { match reg { OwnedValue::Integer(x) => { match i64::checked_abs(*x) { Some(y) => Ok(OwnedValue::Integer(y)), // Special case: if we do the abs of "-9223372036854775808", it causes overflow. // return IntegerOverflow error None => Err(LimboError::IntegerOverflow), } } OwnedValue::Float(x) => { if x < &0.0 { Ok(OwnedValue::Float(-x)) } else { Ok(OwnedValue::Float(*x)) } } OwnedValue::Null => Ok(OwnedValue::Null), _ => Ok(OwnedValue::Float(0.0)), } } fn exec_random() -> OwnedValue { let mut buf = [0u8; 8]; getrandom::getrandom(&mut buf).unwrap(); let random_number = i64::from_ne_bytes(buf); OwnedValue::Integer(random_number) } fn exec_randomblob(reg: &OwnedValue) -> OwnedValue { let length = match reg { OwnedValue::Integer(i) => *i, OwnedValue::Float(f) => *f as i64, OwnedValue::Text(t) => t.as_str().parse().unwrap_or(1), _ => 1, } .max(1) as usize; let mut blob: Vec = vec![0; length]; getrandom::getrandom(&mut blob).expect("Failed to generate random blob"); OwnedValue::Blob(blob) } fn exec_quote(value: &OwnedValue) -> OwnedValue { match value { OwnedValue::Null => OwnedValue::build_text("NULL"), OwnedValue::Integer(_) | OwnedValue::Float(_) => value.to_owned(), OwnedValue::Blob(_) => todo!(), OwnedValue::Text(s) => { let mut quoted = String::with_capacity(s.as_str().len() + 2); quoted.push('\''); for c in s.as_str().chars() { if c == '\0' { break; } else if c == '\'' { quoted.push('\''); quoted.push(c); } else { quoted.push(c); } } quoted.push('\''); OwnedValue::build_text("ed) } } } fn exec_char(values: &[Register]) -> OwnedValue { let result: String = values .iter() .filter_map(|x| { if let OwnedValue::Integer(i) = x.get_owned_value() { Some(*i as u8 as char) } else { None } }) .collect(); OwnedValue::build_text(&result) } fn construct_like_regex(pattern: &str) -> Regex { let mut regex_pattern = String::with_capacity(pattern.len() * 2); regex_pattern.push('^'); for c in pattern.chars() { match c { '\\' => regex_pattern.push_str("\\\\"), '%' => regex_pattern.push_str(".*"), '_' => regex_pattern.push('.'), ch => { if regex_syntax::is_meta_character(c) { regex_pattern.push('\\'); } regex_pattern.push(ch); } } } regex_pattern.push('$'); RegexBuilder::new(®ex_pattern) .case_insensitive(true) .dot_matches_new_line(true) .build() .unwrap() } // Implements LIKE pattern matching. Caches the constructed regex if a cache is provided fn exec_like(regex_cache: Option<&mut HashMap>, pattern: &str, text: &str) -> bool { if let Some(cache) = regex_cache { match cache.get(pattern) { Some(re) => re.is_match(text), None => { let re = construct_like_regex(pattern); let res = re.is_match(text); cache.insert(pattern.to_string(), re); res } } } else { let re = construct_like_regex(pattern); re.is_match(text) } } fn exec_min(regs: &[Register]) -> OwnedValue { regs.iter() .map(|v| v.get_owned_value()) .min() .map(|v| v.to_owned()) .unwrap_or(OwnedValue::Null) } fn exec_max(regs: &[Register]) -> OwnedValue { regs.iter() .map(|v| v.get_owned_value()) .max() .map(|v| v.to_owned()) .unwrap_or(OwnedValue::Null) } fn exec_nullif(first_value: &OwnedValue, second_value: &OwnedValue) -> OwnedValue { if first_value != second_value { first_value.clone() } else { OwnedValue::Null } } fn exec_substring( str_value: &OwnedValue, start_value: &OwnedValue, length_value: Option<&OwnedValue>, ) -> OwnedValue { if let (OwnedValue::Text(str), OwnedValue::Integer(start)) = (str_value, start_value) { let str_len = str.as_str().len() as i64; // The left-most character of X is number 1. // If Y is negative then the first character of the substring is found by counting from the right rather than the left. let first_position = if *start < 0 { str_len.saturating_sub((*start).abs()) } else { *start - 1 }; // If Z is negative then the abs(Z) characters preceding the Y-th character are returned. let last_position = match length_value { Some(OwnedValue::Integer(length)) => first_position + *length, _ => str_len, }; let (start, end) = if first_position <= last_position { (first_position, last_position) } else { (last_position, first_position) }; OwnedValue::build_text( &str.as_str()[start.clamp(-0, str_len) as usize..end.clamp(0, str_len) as usize], ) } else { OwnedValue::Null } } fn exec_instr(reg: &OwnedValue, pattern: &OwnedValue) -> OwnedValue { if reg == &OwnedValue::Null || pattern == &OwnedValue::Null { return OwnedValue::Null; } if let (OwnedValue::Blob(reg), OwnedValue::Blob(pattern)) = (reg, pattern) { let result = reg .windows(pattern.len()) .position(|window| window == *pattern) .map_or(0, |i| i + 1); return OwnedValue::Integer(result as i64); } let reg_str; let reg = match reg { OwnedValue::Text(s) => s.as_str(), _ => { reg_str = reg.to_string(); reg_str.as_str() } }; let pattern_str; let pattern = match pattern { OwnedValue::Text(s) => s.as_str(), _ => { pattern_str = pattern.to_string(); pattern_str.as_str() } }; match reg.find(pattern) { Some(position) => OwnedValue::Integer(position as i64 + 1), None => OwnedValue::Integer(0), } } fn exec_typeof(reg: &OwnedValue) -> OwnedValue { match reg { OwnedValue::Null => OwnedValue::build_text("null"), OwnedValue::Integer(_) => OwnedValue::build_text("integer"), OwnedValue::Float(_) => OwnedValue::build_text("real"), OwnedValue::Text(_) => OwnedValue::build_text("text"), OwnedValue::Blob(_) => OwnedValue::build_text("blob"), } } fn exec_hex(reg: &OwnedValue) -> OwnedValue { match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) | OwnedValue::Blob(_) => { let text = reg.to_string(); OwnedValue::build_text(&hex::encode_upper(text)) } _ => OwnedValue::Null, } } fn exec_unhex(reg: &OwnedValue, ignored_chars: Option<&OwnedValue>) -> OwnedValue { match reg { OwnedValue::Null => OwnedValue::Null, _ => match ignored_chars { None => match hex::decode(reg.to_string()) { Ok(bytes) => OwnedValue::Blob(bytes), Err(_) => OwnedValue::Null, }, Some(ignore) => match ignore { OwnedValue::Text(_) => { let pat = ignore.to_string(); let trimmed = reg .to_string() .trim_start_matches(|x| pat.contains(x)) .trim_end_matches(|x| pat.contains(x)) .to_string(); match hex::decode(trimmed) { Ok(bytes) => OwnedValue::Blob(bytes), Err(_) => OwnedValue::Null, } } _ => OwnedValue::Null, }, }, } } fn exec_unicode(reg: &OwnedValue) -> OwnedValue { match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) | OwnedValue::Blob(_) => { let text = reg.to_string(); if let Some(first_char) = text.chars().next() { OwnedValue::Integer(first_char as u32 as i64) } else { OwnedValue::Null } } _ => OwnedValue::Null, } } fn _to_float(reg: &OwnedValue) -> f64 { match reg { OwnedValue::Text(x) => match cast_text_to_numeric(x.as_str()) { OwnedValue::Integer(i) => i as f64, OwnedValue::Float(f) => f, _ => unreachable!(), }, OwnedValue::Integer(x) => *x as f64, OwnedValue::Float(x) => *x, _ => 0.0, } } fn exec_round(reg: &OwnedValue, precision: Option<&OwnedValue>) -> OwnedValue { let reg = _to_float(reg); let round = |reg: f64, f: f64| { let precision = if f < 1.0 { 0.0 } else { f }; OwnedValue::Float(reg.round_to_precision(precision as i32)) }; match precision { Some(OwnedValue::Text(x)) => match cast_text_to_numeric(x.as_str()) { OwnedValue::Integer(i) => round(reg, i as f64), OwnedValue::Float(f) => round(reg, f), _ => unreachable!(), }, Some(OwnedValue::Integer(i)) => round(reg, *i as f64), Some(OwnedValue::Float(f)) => round(reg, *f), None => round(reg, 0.0), _ => OwnedValue::Null, } } // Implements TRIM pattern matching. fn exec_trim(reg: &OwnedValue, pattern: Option<&OwnedValue>) -> OwnedValue { match (reg, pattern) { (reg, Some(pattern)) => match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { let pattern_chars: Vec = pattern.to_string().chars().collect(); OwnedValue::build_text(reg.to_string().trim_matches(&pattern_chars[..])) } _ => reg.to_owned(), }, (OwnedValue::Text(t), None) => OwnedValue::build_text(t.as_str().trim()), (reg, _) => reg.to_owned(), } } // Implements LTRIM pattern matching. fn exec_ltrim(reg: &OwnedValue, pattern: Option<&OwnedValue>) -> OwnedValue { match (reg, pattern) { (reg, Some(pattern)) => match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { let pattern_chars: Vec = pattern.to_string().chars().collect(); OwnedValue::build_text(reg.to_string().trim_start_matches(&pattern_chars[..])) } _ => reg.to_owned(), }, (OwnedValue::Text(t), None) => OwnedValue::build_text(t.as_str().trim_start()), (reg, _) => reg.to_owned(), } } // Implements RTRIM pattern matching. fn exec_rtrim(reg: &OwnedValue, pattern: Option<&OwnedValue>) -> OwnedValue { match (reg, pattern) { (reg, Some(pattern)) => match reg { OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { let pattern_chars: Vec = pattern.to_string().chars().collect(); OwnedValue::build_text(reg.to_string().trim_end_matches(&pattern_chars[..])) } _ => reg.to_owned(), }, (OwnedValue::Text(t), None) => OwnedValue::build_text(t.as_str().trim_end()), (reg, _) => reg.to_owned(), } } fn exec_zeroblob(req: &OwnedValue) -> OwnedValue { let length: i64 = match req { OwnedValue::Integer(i) => *i, OwnedValue::Float(f) => *f as i64, OwnedValue::Text(s) => s.as_str().parse().unwrap_or(0), _ => 0, }; OwnedValue::Blob(vec![0; length.max(0) as usize]) } // exec_if returns whether you should jump fn exec_if(reg: &OwnedValue, jump_if_null: bool, not: bool) -> bool { match reg { OwnedValue::Integer(0) | OwnedValue::Float(0.0) => not, OwnedValue::Integer(_) | OwnedValue::Float(_) => !not, OwnedValue::Null => jump_if_null, _ => false, } } fn exec_cast(value: &OwnedValue, datatype: &str) -> OwnedValue { if matches!(value, OwnedValue::Null) { return OwnedValue::Null; } match affinity(datatype) { // NONE Casting a value to a type-name with no affinity causes the value to be converted into a BLOB. Casting to a BLOB consists of first casting the value to TEXT in the encoding of the database connection, then interpreting the resulting byte sequence as a BLOB instead of as TEXT. // Historically called NONE, but it's the same as BLOB Affinity::Blob => { // Convert to TEXT first, then interpret as BLOB // TODO: handle encoding let text = value.to_string(); 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. Affinity::Text => { // Convert everything to text representation // TODO: handle encoding and whatever sqlite3_snprintf does OwnedValue::build_text(&value.to_string()) } Affinity::Real => match value { OwnedValue::Blob(b) => { // Convert BLOB to TEXT first let text = String::from_utf8_lossy(b); cast_text_to_real(&text) } OwnedValue::Text(t) => cast_text_to_real(t.as_str()), OwnedValue::Integer(i) => OwnedValue::Float(*i as f64), OwnedValue::Float(f) => OwnedValue::Float(*f), _ => OwnedValue::Float(0.0), }, Affinity::Integer => match value { OwnedValue::Blob(b) => { // Convert BLOB to TEXT first let text = String::from_utf8_lossy(b); cast_text_to_integer(&text) } OwnedValue::Text(t) => cast_text_to_integer(t.as_str()), OwnedValue::Integer(i) => OwnedValue::Integer(*i), // A cast of a REAL value into an INTEGER results in the integer between the REAL value and zero // that is closest to the REAL value. If a REAL is greater than the greatest possible signed integer (+9223372036854775807) // then the result is the greatest possible signed integer and if the REAL is less than the least possible signed integer (-9223372036854775808) // then the result is the least possible signed integer. OwnedValue::Float(f) => { let i = f.trunc() as i128; if i > i64::MAX as i128 { OwnedValue::Integer(i64::MAX) } else if i < i64::MIN as i128 { OwnedValue::Integer(i64::MIN) } else { OwnedValue::Integer(i as i64) } } _ => OwnedValue::Integer(0), }, Affinity::Numeric => match value { OwnedValue::Blob(b) => { let text = String::from_utf8_lossy(b); cast_text_to_numeric(&text) } OwnedValue::Text(t) => cast_text_to_numeric(t.as_str()), OwnedValue::Integer(i) => OwnedValue::Integer(*i), OwnedValue::Float(f) => OwnedValue::Float(*f), _ => value.clone(), // TODO probably wrong }, } } fn exec_replace(source: &OwnedValue, pattern: &OwnedValue, replacement: &OwnedValue) -> OwnedValue { // The replace(X,Y,Z) function returns a string formed by substituting string Z for every occurrence of // string Y in string X. The BINARY collating sequence is used for comparisons. If Y is an empty string // then return X unchanged. If Z is not initially a string, it is cast to a UTF-8 string prior to processing. // If any of the arguments is NULL, the result is NULL. if matches!(source, OwnedValue::Null) || matches!(pattern, OwnedValue::Null) || matches!(replacement, OwnedValue::Null) { return OwnedValue::Null; } let source = exec_cast(source, "TEXT"); let pattern = exec_cast(pattern, "TEXT"); let replacement = exec_cast(replacement, "TEXT"); // If any of the casts failed, panic as text casting is not expected to fail. match (&source, &pattern, &replacement) { (OwnedValue::Text(source), OwnedValue::Text(pattern), OwnedValue::Text(replacement)) => { if pattern.as_str().is_empty() { return OwnedValue::Text(source.clone()); } let result = source .as_str() .replace(pattern.as_str(), replacement.as_str()); OwnedValue::build_text(&result) } _ => unreachable!("text cast should never fail"), } } fn execute_sqlite_version(version_integer: i64) -> String { let major = version_integer / 1_000_000; let minor = (version_integer % 1_000_000) / 1_000; let release = version_integer % 1_000; format!("{}.{}.{}", major, minor, release) } fn to_f64(reg: &OwnedValue) -> Option { match reg { OwnedValue::Integer(i) => Some(*i as f64), OwnedValue::Float(f) => Some(*f), OwnedValue::Text(t) => t.as_str().parse::().ok(), _ => None, } } fn exec_math_unary(reg: &OwnedValue, function: &MathFunc) -> OwnedValue { // In case of some functions and integer input, return the input as is if let OwnedValue::Integer(_) = reg { if matches! { function, MathFunc::Ceil | MathFunc::Ceiling | MathFunc::Floor | MathFunc::Trunc } { return reg.clone(); } } let f = match to_f64(reg) { Some(f) => f, None => return OwnedValue::Null, }; let result = match function { MathFunc::Acos => libm::acos(f), MathFunc::Acosh => libm::acosh(f), MathFunc::Asin => libm::asin(f), MathFunc::Asinh => libm::asinh(f), MathFunc::Atan => libm::atan(f), MathFunc::Atanh => libm::atanh(f), MathFunc::Ceil | MathFunc::Ceiling => libm::ceil(f), MathFunc::Cos => libm::cos(f), MathFunc::Cosh => libm::cosh(f), MathFunc::Degrees => f.to_degrees(), MathFunc::Exp => libm::exp(f), MathFunc::Floor => libm::floor(f), MathFunc::Ln => libm::log(f), MathFunc::Log10 => libm::log10(f), MathFunc::Log2 => libm::log2(f), MathFunc::Radians => f.to_radians(), MathFunc::Sin => libm::sin(f), MathFunc::Sinh => libm::sinh(f), MathFunc::Sqrt => libm::sqrt(f), MathFunc::Tan => libm::tan(f), MathFunc::Tanh => libm::tanh(f), MathFunc::Trunc => libm::trunc(f), _ => unreachable!("Unexpected mathematical unary function {:?}", function), }; if result.is_nan() { OwnedValue::Null } else { OwnedValue::Float(result) } } fn exec_math_binary(lhs: &OwnedValue, rhs: &OwnedValue, function: &MathFunc) -> OwnedValue { let lhs = match to_f64(lhs) { Some(f) => f, None => return OwnedValue::Null, }; let rhs = match to_f64(rhs) { Some(f) => f, None => return OwnedValue::Null, }; let result = match function { MathFunc::Atan2 => libm::atan2(lhs, rhs), MathFunc::Mod => libm::fmod(lhs, rhs), MathFunc::Pow | MathFunc::Power => libm::pow(lhs, rhs), _ => unreachable!("Unexpected mathematical binary function {:?}", function), }; if result.is_nan() { OwnedValue::Null } else { OwnedValue::Float(result) } } fn exec_math_log(arg: &OwnedValue, base: Option<&OwnedValue>) -> OwnedValue { let f = match to_f64(arg) { Some(f) => f, None => return OwnedValue::Null, }; let base = match base { Some(base) => match to_f64(base) { Some(f) => f, None => return OwnedValue::Null, }, None => 10.0, }; if f <= 0.0 || base <= 0.0 || base == 1.0 { return OwnedValue::Null; } let log_x = libm::log(f); let log_base = libm::log(base); let result = log_x / log_base; OwnedValue::Float(result) } #[cfg(test)] mod tests { use crate::vdbe::{execute::exec_replace, Bitfield, Register}; use super::{ exec_abs, exec_char, exec_hex, exec_if, exec_instr, exec_length, exec_like, exec_lower, exec_ltrim, exec_max, exec_min, exec_nullif, exec_quote, exec_random, exec_randomblob, exec_round, exec_rtrim, exec_sign, exec_soundex, exec_substring, exec_trim, exec_typeof, exec_unhex, exec_unicode, exec_upper, exec_zeroblob, execute_sqlite_version, OwnedValue, }; use std::collections::HashMap; #[test] fn test_length() { let input_str = OwnedValue::build_text("bob"); let expected_len = OwnedValue::Integer(3); assert_eq!(exec_length(&input_str), expected_len); let input_integer = OwnedValue::Integer(123); let expected_len = OwnedValue::Integer(3); assert_eq!(exec_length(&input_integer), expected_len); let input_float = OwnedValue::Float(123.456); let expected_len = OwnedValue::Integer(7); assert_eq!(exec_length(&input_float), expected_len); let expected_blob = OwnedValue::Blob("example".as_bytes().to_vec()); let expected_len = OwnedValue::Integer(7); assert_eq!(exec_length(&expected_blob), expected_len); } #[test] fn test_quote() { let input = OwnedValue::build_text("abc\0edf"); let expected = OwnedValue::build_text("'abc'"); assert_eq!(exec_quote(&input), expected); let input = OwnedValue::Integer(123); let expected = OwnedValue::Integer(123); assert_eq!(exec_quote(&input), expected); let input = OwnedValue::build_text("hello''world"); let expected = OwnedValue::build_text("'hello''''world'"); assert_eq!(exec_quote(&input), expected); } #[test] fn test_typeof() { let input = OwnedValue::Null; let expected: OwnedValue = OwnedValue::build_text("null"); assert_eq!(exec_typeof(&input), expected); let input = OwnedValue::Integer(123); let expected: OwnedValue = OwnedValue::build_text("integer"); assert_eq!(exec_typeof(&input), expected); let input = OwnedValue::Float(123.456); let expected: OwnedValue = OwnedValue::build_text("real"); assert_eq!(exec_typeof(&input), expected); let input = OwnedValue::build_text("hello"); let expected: OwnedValue = OwnedValue::build_text("text"); assert_eq!(exec_typeof(&input), expected); let input = OwnedValue::Blob("limbo".as_bytes().to_vec()); let expected: OwnedValue = OwnedValue::build_text("blob"); assert_eq!(exec_typeof(&input), expected); } #[test] fn test_unicode() { assert_eq!( exec_unicode(&OwnedValue::build_text("a")), OwnedValue::Integer(97) ); assert_eq!( exec_unicode(&OwnedValue::build_text("😊")), OwnedValue::Integer(128522) ); assert_eq!(exec_unicode(&OwnedValue::build_text("")), OwnedValue::Null); assert_eq!( exec_unicode(&OwnedValue::Integer(23)), OwnedValue::Integer(50) ); assert_eq!( exec_unicode(&OwnedValue::Integer(0)), OwnedValue::Integer(48) ); assert_eq!( exec_unicode(&OwnedValue::Float(0.0)), OwnedValue::Integer(48) ); assert_eq!( exec_unicode(&OwnedValue::Float(23.45)), OwnedValue::Integer(50) ); assert_eq!(exec_unicode(&OwnedValue::Null), OwnedValue::Null); assert_eq!( exec_unicode(&OwnedValue::Blob("example".as_bytes().to_vec())), OwnedValue::Integer(101) ); } #[test] fn test_min_max() { let input_int_vec = vec![ Register::OwnedValue(OwnedValue::Integer(-1)), Register::OwnedValue(OwnedValue::Integer(10)), ]; assert_eq!(exec_min(&input_int_vec), OwnedValue::Integer(-1)); assert_eq!(exec_max(&input_int_vec), OwnedValue::Integer(10)); let str1 = Register::OwnedValue(OwnedValue::build_text("A")); let str2 = Register::OwnedValue(OwnedValue::build_text("z")); let input_str_vec = vec![str2, str1.clone()]; assert_eq!(exec_min(&input_str_vec), OwnedValue::build_text("A")); assert_eq!(exec_max(&input_str_vec), OwnedValue::build_text("z")); let input_null_vec = vec![ Register::OwnedValue(OwnedValue::Null), Register::OwnedValue(OwnedValue::Null), ]; assert_eq!(exec_min(&input_null_vec), OwnedValue::Null); assert_eq!(exec_max(&input_null_vec), OwnedValue::Null); let input_mixed_vec = vec![Register::OwnedValue(OwnedValue::Integer(10)), str1]; assert_eq!(exec_min(&input_mixed_vec), OwnedValue::Integer(10)); assert_eq!(exec_max(&input_mixed_vec), OwnedValue::build_text("A")); } #[test] fn test_trim() { let input_str = OwnedValue::build_text(" Bob and Alice "); let expected_str = OwnedValue::build_text("Bob and Alice"); assert_eq!(exec_trim(&input_str, None), expected_str); let input_str = OwnedValue::build_text(" Bob and Alice "); let pattern_str = OwnedValue::build_text("Bob and"); let expected_str = OwnedValue::build_text("Alice"); assert_eq!(exec_trim(&input_str, Some(&pattern_str)), expected_str); } #[test] fn test_ltrim() { let input_str = OwnedValue::build_text(" Bob and Alice "); let expected_str = OwnedValue::build_text("Bob and Alice "); assert_eq!(exec_ltrim(&input_str, None), expected_str); let input_str = OwnedValue::build_text(" Bob and Alice "); let pattern_str = OwnedValue::build_text("Bob and"); let expected_str = OwnedValue::build_text("Alice "); assert_eq!(exec_ltrim(&input_str, Some(&pattern_str)), expected_str); } #[test] fn test_rtrim() { let input_str = OwnedValue::build_text(" Bob and Alice "); let expected_str = OwnedValue::build_text(" Bob and Alice"); assert_eq!(exec_rtrim(&input_str, None), expected_str); let input_str = OwnedValue::build_text(" Bob and Alice "); let pattern_str = OwnedValue::build_text("Bob and"); let expected_str = OwnedValue::build_text(" Bob and Alice"); assert_eq!(exec_rtrim(&input_str, Some(&pattern_str)), expected_str); let input_str = OwnedValue::build_text(" Bob and Alice "); let pattern_str = OwnedValue::build_text("and Alice"); let expected_str = OwnedValue::build_text(" Bob"); assert_eq!(exec_rtrim(&input_str, Some(&pattern_str)), expected_str); } #[test] fn test_soundex() { let input_str = OwnedValue::build_text("Pfister"); let expected_str = OwnedValue::build_text("P236"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("husobee"); let expected_str = OwnedValue::build_text("H210"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("Tymczak"); let expected_str = OwnedValue::build_text("T522"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("Ashcraft"); let expected_str = OwnedValue::build_text("A261"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("Robert"); let expected_str = OwnedValue::build_text("R163"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("Rupert"); let expected_str = OwnedValue::build_text("R163"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("Rubin"); let expected_str = OwnedValue::build_text("R150"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("Kant"); let expected_str = OwnedValue::build_text("K530"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("Knuth"); let expected_str = OwnedValue::build_text("K530"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("x"); let expected_str = OwnedValue::build_text("X000"); assert_eq!(exec_soundex(&input_str), expected_str); let input_str = OwnedValue::build_text("闪电五连鞭"); let expected_str = OwnedValue::build_text("?000"); assert_eq!(exec_soundex(&input_str), expected_str); } #[test] fn test_upper_case() { let input_str = OwnedValue::build_text("Limbo"); let expected_str = OwnedValue::build_text("LIMBO"); assert_eq!(exec_upper(&input_str).unwrap(), expected_str); let input_int = OwnedValue::Integer(10); assert_eq!(exec_upper(&input_int).unwrap(), input_int); assert_eq!(exec_upper(&OwnedValue::Null).unwrap(), OwnedValue::Null) } #[test] fn test_lower_case() { let input_str = OwnedValue::build_text("Limbo"); let expected_str = OwnedValue::build_text("limbo"); assert_eq!(exec_lower(&input_str).unwrap(), expected_str); let input_int = OwnedValue::Integer(10); assert_eq!(exec_lower(&input_int).unwrap(), input_int); assert_eq!(exec_lower(&OwnedValue::Null).unwrap(), OwnedValue::Null) } #[test] fn test_hex() { let input_str = OwnedValue::build_text("limbo"); let expected_val = OwnedValue::build_text("6C696D626F"); assert_eq!(exec_hex(&input_str), expected_val); let input_int = OwnedValue::Integer(100); let expected_val = OwnedValue::build_text("313030"); assert_eq!(exec_hex(&input_int), expected_val); let input_float = OwnedValue::Float(12.34); let expected_val = OwnedValue::build_text("31322E3334"); assert_eq!(exec_hex(&input_float), expected_val); } #[test] fn test_unhex() { let input = OwnedValue::build_text("6f"); let expected = OwnedValue::Blob(vec![0x6f]); assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::build_text("6f"); let expected = OwnedValue::Blob(vec![0x6f]); assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::build_text("611"); let expected = OwnedValue::Null; assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::build_text(""); let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::build_text("61x"); let expected = OwnedValue::Null; assert_eq!(exec_unhex(&input, None), expected); let input = OwnedValue::Null; let expected = OwnedValue::Null; assert_eq!(exec_unhex(&input, None), expected); } #[test] fn test_abs() { let int_positive_reg = OwnedValue::Integer(10); let int_negative_reg = OwnedValue::Integer(-10); assert_eq!(exec_abs(&int_positive_reg).unwrap(), int_positive_reg); assert_eq!(exec_abs(&int_negative_reg).unwrap(), int_positive_reg); let float_positive_reg = OwnedValue::Integer(10); let float_negative_reg = OwnedValue::Integer(-10); assert_eq!(exec_abs(&float_positive_reg).unwrap(), float_positive_reg); assert_eq!(exec_abs(&float_negative_reg).unwrap(), float_positive_reg); assert_eq!( exec_abs(&OwnedValue::build_text("a")).unwrap(), OwnedValue::Float(0.0) ); assert_eq!(exec_abs(&OwnedValue::Null).unwrap(), OwnedValue::Null); // ABS(i64::MIN) should return RuntimeError assert!(exec_abs(&OwnedValue::Integer(i64::MIN)).is_err()); } #[test] fn test_char() { assert_eq!( exec_char(&[ Register::OwnedValue(OwnedValue::Integer(108)), Register::OwnedValue(OwnedValue::Integer(105)) ]), OwnedValue::build_text("li") ); assert_eq!(exec_char(&[]), OwnedValue::build_text("")); assert_eq!( exec_char(&[Register::OwnedValue(OwnedValue::Null)]), OwnedValue::build_text("") ); assert_eq!( exec_char(&[Register::OwnedValue(OwnedValue::build_text("a"))]), OwnedValue::build_text("") ); } #[test] fn test_like_with_escape_or_regexmeta_chars() { assert!(exec_like(None, r#"\%A"#, r#"\A"#)); assert!(exec_like(None, "%a%a", "aaaa")); } #[test] fn test_like_no_cache() { assert!(exec_like(None, "a%", "aaaa")); assert!(exec_like(None, "%a%a", "aaaa")); assert!(!exec_like(None, "%a.a", "aaaa")); assert!(!exec_like(None, "a.a%", "aaaa")); assert!(!exec_like(None, "%a.ab", "aaaa")); } #[test] fn test_like_with_cache() { let mut cache = HashMap::new(); assert!(exec_like(Some(&mut cache), "a%", "aaaa")); assert!(exec_like(Some(&mut cache), "%a%a", "aaaa")); assert!(!exec_like(Some(&mut cache), "%a.a", "aaaa")); assert!(!exec_like(Some(&mut cache), "a.a%", "aaaa")); assert!(!exec_like(Some(&mut cache), "%a.ab", "aaaa")); // again after values have been cached assert!(exec_like(Some(&mut cache), "a%", "aaaa")); assert!(exec_like(Some(&mut cache), "%a%a", "aaaa")); assert!(!exec_like(Some(&mut cache), "%a.a", "aaaa")); assert!(!exec_like(Some(&mut cache), "a.a%", "aaaa")); assert!(!exec_like(Some(&mut cache), "%a.ab", "aaaa")); } #[test] fn test_random() { match exec_random() { OwnedValue::Integer(value) => { // Check that the value is within the range of i64 assert!( (i64::MIN..=i64::MAX).contains(&value), "Random number out of range" ); } _ => panic!("exec_random did not return an Integer variant"), } } #[test] fn test_exec_randomblob() { struct TestCase { input: OwnedValue, expected_len: usize, } let test_cases = vec![ TestCase { input: OwnedValue::Integer(5), expected_len: 5, }, TestCase { input: OwnedValue::Integer(0), expected_len: 1, }, TestCase { input: OwnedValue::Integer(-1), expected_len: 1, }, TestCase { input: OwnedValue::build_text(""), expected_len: 1, }, TestCase { input: OwnedValue::build_text("5"), expected_len: 5, }, TestCase { input: OwnedValue::build_text("0"), expected_len: 1, }, TestCase { input: OwnedValue::build_text("-1"), expected_len: 1, }, TestCase { input: OwnedValue::Float(2.9), expected_len: 2, }, TestCase { input: OwnedValue::Float(-3.15), expected_len: 1, }, TestCase { input: OwnedValue::Null, expected_len: 1, }, ]; for test_case in &test_cases { let result = exec_randomblob(&test_case.input); match result { OwnedValue::Blob(blob) => { assert_eq!(blob.len(), test_case.expected_len); } _ => panic!("exec_randomblob did not return a Blob variant"), } } } #[test] fn test_exec_round() { let input_val = OwnedValue::Float(123.456); let expected_val = OwnedValue::Float(123.0); assert_eq!(exec_round(&input_val, None), expected_val); let input_val = OwnedValue::Float(123.456); let precision_val = OwnedValue::Integer(2); let expected_val = OwnedValue::Float(123.46); assert_eq!(exec_round(&input_val, Some(&precision_val)), expected_val); let input_val = OwnedValue::Float(123.456); let precision_val = OwnedValue::build_text("1"); let expected_val = OwnedValue::Float(123.5); assert_eq!(exec_round(&input_val, Some(&precision_val)), expected_val); let input_val = OwnedValue::build_text("123.456"); let precision_val = OwnedValue::Integer(2); let expected_val = OwnedValue::Float(123.46); assert_eq!(exec_round(&input_val, Some(&precision_val)), expected_val); let input_val = OwnedValue::Integer(123); let precision_val = OwnedValue::Integer(1); let expected_val = OwnedValue::Float(123.0); assert_eq!(exec_round(&input_val, Some(&precision_val)), expected_val); let input_val = OwnedValue::Float(100.123); let expected_val = OwnedValue::Float(100.0); assert_eq!(exec_round(&input_val, None), expected_val); let input_val = OwnedValue::Float(100.123); let expected_val = OwnedValue::Null; assert_eq!( exec_round(&input_val, Some(&OwnedValue::Null)), expected_val ); } #[test] fn test_exec_if() { let reg = OwnedValue::Integer(0); assert!(!exec_if(®, false, false)); assert!(exec_if(®, false, true)); let reg = OwnedValue::Integer(1); assert!(exec_if(®, false, false)); assert!(!exec_if(®, false, true)); let reg = OwnedValue::Null; assert!(!exec_if(®, false, false)); assert!(!exec_if(®, false, true)); let reg = OwnedValue::Null; assert!(exec_if(®, true, false)); assert!(exec_if(®, true, true)); let reg = OwnedValue::Null; assert!(!exec_if(®, false, false)); assert!(!exec_if(®, false, true)); } #[test] fn test_nullif() { assert_eq!( exec_nullif(&OwnedValue::Integer(1), &OwnedValue::Integer(1)), OwnedValue::Null ); assert_eq!( exec_nullif(&OwnedValue::Float(1.1), &OwnedValue::Float(1.1)), OwnedValue::Null ); assert_eq!( exec_nullif( &OwnedValue::build_text("limbo"), &OwnedValue::build_text("limbo") ), OwnedValue::Null ); assert_eq!( exec_nullif(&OwnedValue::Integer(1), &OwnedValue::Integer(2)), OwnedValue::Integer(1) ); assert_eq!( exec_nullif(&OwnedValue::Float(1.1), &OwnedValue::Float(1.2)), OwnedValue::Float(1.1) ); assert_eq!( exec_nullif( &OwnedValue::build_text("limbo"), &OwnedValue::build_text("limb") ), OwnedValue::build_text("limbo") ); } #[test] fn test_substring() { let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(1); let length_value = OwnedValue::Integer(3); let expected_val = OwnedValue::build_text("lim"); assert_eq!( exec_substring(&str_value, &start_value, Some(&length_value)), expected_val ); let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(1); let length_value = OwnedValue::Integer(10); let expected_val = OwnedValue::build_text("limbo"); assert_eq!( exec_substring(&str_value, &start_value, Some(&length_value)), expected_val ); let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(10); let length_value = OwnedValue::Integer(3); let expected_val = OwnedValue::build_text(""); assert_eq!( exec_substring(&str_value, &start_value, Some(&length_value)), expected_val ); let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(3); let length_value = OwnedValue::Null; let expected_val = OwnedValue::build_text("mbo"); assert_eq!( exec_substring(&str_value, &start_value, Some(&length_value)), expected_val ); let str_value = OwnedValue::build_text("limbo"); let start_value = OwnedValue::Integer(10); let length_value = OwnedValue::Null; let expected_val = OwnedValue::build_text(""); assert_eq!( exec_substring(&str_value, &start_value, Some(&length_value)), expected_val ); } #[test] fn test_exec_instr() { let input = OwnedValue::build_text("limbo"); let pattern = OwnedValue::build_text("im"); let expected = OwnedValue::Integer(2); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text("limbo"); let pattern = OwnedValue::build_text("limbo"); let expected = OwnedValue::Integer(1); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text("limbo"); let pattern = OwnedValue::build_text("o"); let expected = OwnedValue::Integer(5); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text("liiiiimbo"); let pattern = OwnedValue::build_text("ii"); let expected = OwnedValue::Integer(2); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text("limbo"); let pattern = OwnedValue::build_text("limboX"); let expected = OwnedValue::Integer(0); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text("limbo"); let pattern = OwnedValue::build_text(""); let expected = OwnedValue::Integer(1); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text(""); let pattern = OwnedValue::build_text("limbo"); let expected = OwnedValue::Integer(0); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text(""); let pattern = OwnedValue::build_text(""); let expected = OwnedValue::Integer(1); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Null; let pattern = OwnedValue::Null; let expected = OwnedValue::Null; assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::build_text("limbo"); let pattern = OwnedValue::Null; let expected = OwnedValue::Null; assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Null; let pattern = OwnedValue::build_text("limbo"); let expected = OwnedValue::Null; assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Integer(123); let pattern = OwnedValue::Integer(2); let expected = OwnedValue::Integer(2); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Integer(123); let pattern = OwnedValue::Integer(5); let expected = OwnedValue::Integer(0); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Float(12.34); let pattern = OwnedValue::Float(2.3); let expected = OwnedValue::Integer(2); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Float(12.34); let pattern = OwnedValue::Float(5.6); let expected = OwnedValue::Integer(0); assert_eq!(exec_instr(&input, &pattern), expected); let input = OwnedValue::Float(12.34); let pattern = OwnedValue::build_text("."); let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); 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(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(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(vec![0x63, 0x64]); let expected = OwnedValue::Integer(3); assert_eq!(exec_instr(&input, &pattern), expected); } #[test] fn test_exec_sign() { let input = OwnedValue::Integer(42); let expected = Some(OwnedValue::Integer(1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Integer(-42); let expected = Some(OwnedValue::Integer(-1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Integer(0); let expected = Some(OwnedValue::Integer(0)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Float(0.0); let expected = Some(OwnedValue::Integer(0)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Float(0.1); let expected = Some(OwnedValue::Integer(1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Float(42.0); let expected = Some(OwnedValue::Integer(1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Float(-42.0); let expected = Some(OwnedValue::Integer(-1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::build_text("abc"); let expected = Some(OwnedValue::Null); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::build_text("42"); let expected = Some(OwnedValue::Integer(1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::build_text("-42"); let expected = Some(OwnedValue::Integer(-1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::build_text("0"); let expected = Some(OwnedValue::Integer(0)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Blob(b"abc".to_vec()); let expected = Some(OwnedValue::Null); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Blob(b"42".to_vec()); let expected = Some(OwnedValue::Integer(1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Blob(b"-42".to_vec()); let expected = Some(OwnedValue::Integer(-1)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Blob(b"0".to_vec()); let expected = Some(OwnedValue::Integer(0)); assert_eq!(exec_sign(&input), expected); let input = OwnedValue::Null; let expected = Some(OwnedValue::Null); assert_eq!(exec_sign(&input), expected); } #[test] fn test_exec_zeroblob() { let input = OwnedValue::Integer(0); let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Null; let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Integer(4); let expected = OwnedValue::Blob(vec![0; 4]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Integer(-1); let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::build_text("5"); let expected = OwnedValue::Blob(vec![0; 5]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::build_text("-5"); let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::build_text("text"); let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Float(2.6); let expected = OwnedValue::Blob(vec![0; 2]); assert_eq!(exec_zeroblob(&input), expected); let input = OwnedValue::Blob(vec![1]); let expected = OwnedValue::Blob(vec![]); assert_eq!(exec_zeroblob(&input), expected); } #[test] fn test_execute_sqlite_version() { let version_integer = 3046001; let expected = "3.46.1"; assert_eq!(execute_sqlite_version(version_integer), expected); } #[test] fn test_replace() { let input_str = OwnedValue::build_text("bob"); let pattern_str = OwnedValue::build_text("b"); let replace_str = OwnedValue::build_text("a"); let expected_str = OwnedValue::build_text("aoa"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bob"); let pattern_str = OwnedValue::build_text("b"); let replace_str = OwnedValue::build_text(""); let expected_str = OwnedValue::build_text("o"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bob"); let pattern_str = OwnedValue::build_text("b"); let replace_str = OwnedValue::build_text("abc"); let expected_str = OwnedValue::build_text("abcoabc"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bob"); let pattern_str = OwnedValue::build_text("a"); let replace_str = OwnedValue::build_text("b"); let expected_str = OwnedValue::build_text("bob"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bob"); let pattern_str = OwnedValue::build_text(""); let replace_str = OwnedValue::build_text("a"); let expected_str = OwnedValue::build_text("bob"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bob"); let pattern_str = OwnedValue::Null; let replace_str = OwnedValue::build_text("a"); let expected_str = OwnedValue::Null; assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bo5"); let pattern_str = OwnedValue::Integer(5); let replace_str = OwnedValue::build_text("a"); let expected_str = OwnedValue::build_text("boa"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bo5.0"); let pattern_str = OwnedValue::Float(5.0); let replace_str = OwnedValue::build_text("a"); let expected_str = OwnedValue::build_text("boa"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bo5"); let pattern_str = OwnedValue::Float(5.0); let replace_str = OwnedValue::build_text("a"); let expected_str = OwnedValue::build_text("bo5"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); let input_str = OwnedValue::build_text("bo5.0"); let pattern_str = OwnedValue::Float(5.0); let replace_str = OwnedValue::Float(6.0); let expected_str = OwnedValue::build_text("bo6.0"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); // todo: change this test to use (0.1 + 0.2) instead of 0.3 when decimals are implemented. let input_str = OwnedValue::build_text("tes3"); let pattern_str = OwnedValue::Integer(3); let replace_str = OwnedValue::Float(0.3); let expected_str = OwnedValue::build_text("tes0.3"); assert_eq!( exec_replace(&input_str, &pattern_str, &replace_str), expected_str ); } #[test] fn test_bitfield() { let mut bitfield = Bitfield::<4>::new(); for i in 0..256 { bitfield.set(i); assert!(bitfield.get(i)); for j in 0..i { assert!(bitfield.get(j)); } for j in i + 1..256 { assert!(!bitfield.get(j)); } } for i in 0..256 { bitfield.unset(i); assert!(!bitfield.get(i)); } } }