From 825aeb3f83d2cedc29fdce821a33409aa81c51a2 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Wed, 9 Apr 2025 10:56:37 -0300 Subject: [PATCH 1/4] core/vdbe: Add BeginSubrtn bytecode Basically it does the very same thing of Null, but has a different name to differentiate its usage. --- COMPAT.md | 1 + core/vdbe/explain.rs | 11 +++++++++++ core/vdbe/insn.rs | 7 +++++++ 3 files changed, 19 insertions(+) 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/vdbe/explain.rs b/core/vdbe/explain.rs index f2230d2eb..a0ddd6701 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1350,6 +1350,17 @@ 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) + }), + ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 7b30396c0..b748706e7 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -100,6 +100,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, @@ -803,6 +809,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, From c5161311fcaa5b18fa1f8b550c467eaef7a82a37 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Mon, 14 Apr 2025 11:50:28 -0300 Subject: [PATCH 2/4] core/vdbe: Add NotFound bytecode If P4==0 then register P3 holds a blob constructed by MakeRecord. If P4>0 then register P3 is the first of P4 registers that form an unpacked record. Cursor P1 is on an index btree. If the record identified by P3 and P4 is not the prefix of any entry in P1 then a jump is made to P2. If P1 does contain an entry whose prefix matches the P3/P4 record then control falls through to the next instruction and P1 is left pointing at the matching entry. This operation leaves the cursor in a state where it cannot be advanced in either direction. In other words, the Next and Prev opcodes do not work after this operation. --- core/vdbe/execute.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++ core/vdbe/explain.rs | 18 +++++++++++++++++ core/vdbe/insn.rs | 8 ++++++++ 3 files changed, 73 insertions(+) diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 2eafed217..7b151c093 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -4472,6 +4472,53 @@ 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) +} + 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 a0ddd6701..8149b4ba6 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1361,6 +1361,24 @@ pub fn insn_to_str( 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() + ), + ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index b748706e7..8f1fbe580 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -802,6 +802,13 @@ pub enum Insn { Once { target_pc_when_reentered: BranchOffset, }, + /// Search for record in the index cusor, if exists is a no-op otherwise go to target_pc + NotFound { + cursor_id: CursorID, + target_pc: BranchOffset, + record_reg: usize, + num_regs: usize, + }, } impl Insn { @@ -916,6 +923,7 @@ 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, } } } From 58efb9046790b4dd897aef59f44abef8f7efd36a Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Mon, 14 Apr 2025 12:09:56 -0300 Subject: [PATCH 3/4] core: Add Affinity bytecode Apply affinities to a range of P2 registers starting with P1. P4 is a string that is P2 characters long. The N-th character of the string indicates the column affinity that should be used for the N-th memory cell in the range. --- core/schema.rs | 16 +++++++++++++++- core/vdbe/execute.rs | 36 +++++++++++++++++++++++++++++++++++- core/vdbe/explain.rs | 22 ++++++++++++++++++++++ core/vdbe/insn.rs | 12 +++++++++++- 4 files changed, 83 insertions(+), 3 deletions(-) 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 7b151c093..66ced7233 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -4509,7 +4509,7 @@ pub fn op_not_found( return_if_io!(cursor.seek(SeekKey::IndexKey(&record), SeekOp::EQ)) } }; - + if found { state.pc += 1; } else { @@ -4519,6 +4519,40 @@ pub fn op_not_found( 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 8149b4ba6..cbb546a11 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1379,6 +1379,28 @@ pub fn insn_to_str( 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 8f1fbe580..32c3f9550 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::{ @@ -809,6 +812,12 @@ pub enum Insn { 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 { @@ -924,6 +933,7 @@ impl Insn { Insn::OpenEphemeral { .. } | Insn::OpenAutoindex { .. } => execute::op_open_ephemeral, Insn::Once { .. } => execute::op_once, Insn::NotFound { .. } => execute::op_not_found, + Insn::Affinity { .. } => execute::op_affinity, } } } From 2cc492844ee328598ecda933bc3816307ade7a02 Mon Sep 17 00:00:00 2001 From: Diego Reis Date: Tue, 15 Apr 2025 10:38:29 -0300 Subject: [PATCH 4/4] Improve NotFound's docs clarity --- core/vdbe/insn.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 32c3f9550..7f9432fb0 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -805,7 +805,13 @@ pub enum Insn { Once { target_pc_when_reentered: BranchOffset, }, - /// Search for record in the index cusor, if exists is a no-op otherwise go to target_pc + /// 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,