diff --git a/COMPAT.md b/COMPAT.md index 4ac83e6bb..ced9fbb6d 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -427,6 +427,7 @@ Modifiers: | BitNot | Yes | | | BitOr | Yes | | | Blob | Yes | | +| BeginSubrtn | Yes | | | Checkpoint | No | | | Clear | No | | | Close | No | | diff --git a/core/schema.rs b/core/schema.rs index fbd3a86ec..ea6a26279 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -1,5 +1,5 @@ -use crate::VirtualTable; use crate::{util::normalize_ident, Result}; +use crate::{LimboError, VirtualTable}; use core::fmt; use fallible_iterator::FallibleIterator; use limbo_sqlite3_parser::ast::{Expr, Literal, SortOrder, TableOptions}; @@ -585,6 +585,20 @@ impl Affinity { Affinity::Numeric => SQLITE_AFF_NUMERIC, } } + + pub fn from_char(char: char) -> Result { + match char { + SQLITE_AFF_INTEGER => Ok(Affinity::Integer), + SQLITE_AFF_TEXT => Ok(Affinity::Text), + SQLITE_AFF_NONE => Ok(Affinity::Blob), + SQLITE_AFF_REAL => Ok(Affinity::Real), + SQLITE_AFF_NUMERIC => Ok(Affinity::Numeric), + _ => Err(LimboError::InternalError(format!( + "Invalid affinity character: {}", + char + ))), + } + } } impl fmt::Display for Type { diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 0325ffae8..41268a342 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -4494,6 +4494,87 @@ pub fn op_once( Ok(InsnFunctionStepResult::Step) } +pub fn op_not_found( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Rc, + mv_store: Option<&Rc>, +) -> Result { + let Insn::NotFound { + cursor_id, + target_pc, + record_reg, + num_regs, + } = insn + else { + unreachable!("unexpected Insn {:?}", insn) + }; + + let found = { + let mut cursor = state.get_cursor(*cursor_id); + let cursor = cursor.as_btree_mut(); + + if *num_regs == 0 { + let record = match &state.registers[*record_reg] { + Register::Record(r) => r, + _ => { + return Err(LimboError::InternalError( + "NotFound: exepected a record in the register".into(), + )); + } + }; + + return_if_io!(cursor.seek(SeekKey::IndexKey(&record), SeekOp::EQ)) + } else { + let record = make_record(&state.registers, record_reg, num_regs); + return_if_io!(cursor.seek(SeekKey::IndexKey(&record), SeekOp::EQ)) + } + }; + + if found { + state.pc += 1; + } else { + state.pc = target_pc.to_offset_int(); + } + + Ok(InsnFunctionStepResult::Step) +} + +pub fn op_affinity( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Rc, + mv_store: Option<&Rc>, +) -> Result { + let Insn::Affinity { + start_reg, + count, + affinities, + } = insn + else { + unreachable!("unexpected Insn {:?}", insn) + }; + + if affinities.len() != count.get() { + return Err(LimboError::InternalError( + "Affinity: the length of affinities does not match the count".into(), + )); + } + + for (i, affinity_char) in affinities.chars().enumerate().take(count.get()) { + let reg_index = *start_reg + i; + + let affinity = Affinity::from_char(affinity_char)?; + + apply_affinity_char(&mut state.registers[reg_index], affinity); + } + + 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())), diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index f6e1073c3..96afc5d17 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1366,6 +1366,57 @@ pub fn insn_to_str( 0, format!("goto {}", target_pc_when_reentered.to_debug_int()), ), + Insn::BeginSubrtn { dest, dest_end } => ( + "BeginSubrtn", + *dest as i32, + dest_end.map_or(0, |end| end as i32), + 0, + OwnedValue::build_text(""), + 0, + dest_end.map_or(format!("r[{}]=NULL", dest), |end| { + format!("r[{}..{}]=NULL", dest, end) + }), + ), + Insn::NotFound { + cursor_id, + target_pc, + record_reg, + .. + } => ( + "NotFound", + *cursor_id as i32, + target_pc.to_debug_int(), + *record_reg as i32, + OwnedValue::build_text(""), + 0, + format!( + "if (r[{}] != NULL) goto {}", + record_reg, + target_pc.to_debug_int() + ), + ), + Insn::Affinity { + start_reg, + count, + affinities, + } => ( + "Affinity", + *start_reg as i32, + count.get() as i32, + 0, + OwnedValue::build_text(""), + 0, + format!( + "r[{}..{}] = {}", + start_reg, + start_reg + count.get(), + affinities + .chars() + .map(|a| a.to_string()) + .collect::>() + .join(", ") + ), + ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 87a615aee..f1276798f 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -1,4 +1,7 @@ -use std::{num::NonZero, rc::Rc}; +use std::{ + num::{NonZero, NonZeroUsize}, + rc::Rc, +}; use super::{execute, AggFunc, BranchOffset, CursorID, FuncCtx, InsnFunction, PageIdx}; use crate::{ @@ -100,6 +103,12 @@ pub enum Insn { dest: usize, dest_end: Option, }, + /// Mark the beginning of a subroutine tha can be entered in-line. This opcode is identical to Null + /// it has a different name only to make the byte code easier to read and verify + BeginSubrtn { + dest: usize, + dest_end: Option, + }, /// Move the cursor P1 to a null row. Any Column operations that occur while the cursor is on the null row will always write a NULL. NullRow { cursor_id: CursorID, @@ -801,6 +810,25 @@ pub enum Insn { Once { target_pc_when_reentered: BranchOffset, }, + /// Search for record in the index cusor, if any entry for which the key is a prefix exists + /// is a no-op, otherwise go to target_pc + /// Example => + /// For a index key (1,2,3): + /// NotFound((1,2,3)) => No-op + /// NotFound((1,2)) => No-op + /// NotFound((2,2, 1)) => Jump + NotFound { + cursor_id: CursorID, + target_pc: BranchOffset, + record_reg: usize, + num_regs: usize, + }, + /// Apply affinities to a range of registers. Affinities must have the same size of count + Affinity { + start_reg: usize, + count: NonZeroUsize, + affinities: String, + }, } impl Insn { @@ -808,6 +836,7 @@ impl Insn { match self { Insn::Init { .. } => execute::op_init, Insn::Null { .. } => execute::op_null, + Insn::BeginSubrtn { .. } => execute::op_null, Insn::NullRow { .. } => execute::op_null_row, Insn::Add { .. } => execute::op_add, Insn::Subtract { .. } => execute::op_subtract, @@ -915,6 +944,8 @@ impl Insn { Insn::ReadCookie { .. } => execute::op_read_cookie, Insn::OpenEphemeral { .. } | Insn::OpenAutoindex { .. } => execute::op_open_ephemeral, Insn::Once { .. } => execute::op_once, + Insn::NotFound { .. } => execute::op_not_found, + Insn::Affinity { .. } => execute::op_affinity, } } }