From 3e42a62cd08fcdbea19af9e22ce5dbe82afb5f55 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Tue, 8 Apr 2025 10:19:38 +0300 Subject: [PATCH] Add SeekLE/SeekLT operations to VDBE --- core/types.rs | 4 +- core/vdbe/builder.rs | 6 +++ core/vdbe/execute.rs | 113 ++++++++++++++++--------------------------- core/vdbe/explain.rs | 50 +++++++++++-------- core/vdbe/insn.rs | 30 +++++++++++- core/vdbe/mod.rs | 5 +- 6 files changed, 115 insertions(+), 93 deletions(-) diff --git a/core/types.rs b/core/types.rs index 72119349f..cc4495f52 100644 --- a/core/types.rs +++ b/core/types.rs @@ -1203,11 +1203,13 @@ pub enum CursorResult { IO, } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum SeekOp { EQ, GE, GT, + LE, + LT, } #[derive(Clone, PartialEq, Debug)] diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index 19a71a68d..78216204e 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -413,6 +413,12 @@ impl ProgramBuilder { Insn::SeekGT { target_pc, .. } => { resolve(target_pc, "SeekGT"); } + Insn::SeekLE { target_pc, .. } => { + resolve(target_pc, "SeekLE"); + } + Insn::SeekLT { target_pc, .. } => { + resolve(target_pc, "SeekLT"); + } Insn::IdxGE { target_pc, .. } => { resolve(target_pc, "IdxGE"); } diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 654e9a2c5..b282fa524 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -1892,97 +1892,69 @@ pub fn op_deferred_seek( Ok(InsnFunctionStepResult::Step) } -pub fn op_seek_ge( +pub fn op_seek( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, ) -> Result { - let Insn::SeekGE { + 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 { + | Insn::SeekGT { cursor_id, start_reg, num_regs, target_pc, is_index, - } = insn + } + | Insn::SeekLE { + cursor_id, + start_reg, + num_regs, + target_pc, + is_index, + } + | Insn::SeekLT { + cursor_id, + start_reg, + num_regs, + target_pc, + is_index, + }) = insn else { unreachable!("unexpected Insn {:?}", insn) }; - assert!(target_pc.is_offset()); + assert!( + target_pc.is_offset(), + "target_pc should be an offset, is: {:?}", + target_pc + ); + let op = match insn { + Insn::SeekGE { .. } => SeekOp::GE, + Insn::SeekGT { .. } => SeekOp::GT, + Insn::SeekLE { .. } => SeekOp::LE, + Insn::SeekLT { .. } => SeekOp::LT, + _ => unreachable!("unexpected Insn {:?}", insn), + }; + let op_name = match op { + SeekOp::GE => "SeekGE", + SeekOp::GT => "SeekGT", + SeekOp::LE => "SeekLE", + SeekOp::LT => "SeekLT", + _ => unreachable!("unexpected SeekOp {:?}", op), + }; 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)); + let found = return_if_io!(cursor.seek(SeekKey::IndexKey(&record_from_regs), op)); found }; if !found { @@ -2002,14 +1974,15 @@ pub fn op_seek_gt( } OwnedValue::Integer(rowid) => Some(*rowid as u64), _ => { - return Err(LimboError::InternalError( - "SeekGT: the value in the register is not an integer".into(), - )); + return Err(LimboError::InternalError(format!( + "{}: the value in the register is not an integer", + op_name + ))); } }; let found = match rowid { Some(rowid) => { - let found = return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), SeekOp::GT)); + let found = return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), op)); if !found { target_pc.to_offset_int() } else { diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 66c68d9c0..550e6cb5c 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -736,23 +736,35 @@ pub fn insn_to_str( start_reg, num_regs: _, target_pc, - } => ( - "SeekGT", - *cursor_id as i32, - target_pc.to_debug_int(), - *start_reg as i32, - OwnedValue::build_text(""), - 0, - "".to_string(), - ), - Insn::SeekGE { + } + | Insn::SeekGE { + is_index: _, + cursor_id, + start_reg, + num_regs: _, + target_pc, + } + | Insn::SeekLE { + is_index: _, + cursor_id, + start_reg, + num_regs: _, + target_pc, + } + | Insn::SeekLT { is_index: _, cursor_id, start_reg, num_regs: _, target_pc, } => ( - "SeekGE", + match insn { + Insn::SeekGT { .. } => "SeekGT", + Insn::SeekGE { .. } => "SeekGE", + Insn::SeekLE { .. } => "SeekLE", + Insn::SeekLT { .. } => "SeekLT", + _ => unreachable!(), + }, *cursor_id as i32, target_pc.to_debug_int(), *start_reg as i32, @@ -1213,9 +1225,9 @@ pub fn insn_to_str( 0, "".to_string(), ), - Insn::LastAsync { .. } => ( + Insn::LastAsync { cursor_id } => ( "LastAsync", - 0, + *cursor_id as i32, 0, 0, OwnedValue::build_text(""), @@ -1240,27 +1252,27 @@ pub fn insn_to_str( 0, where_clause.clone(), ), - Insn::LastAwait { .. } => ( + Insn::LastAwait { cursor_id, .. } => ( "LastAwait", - 0, + *cursor_id as i32, 0, 0, OwnedValue::build_text(""), 0, "".to_string(), ), - Insn::PrevAsync { .. } => ( + Insn::PrevAsync { cursor_id } => ( "PrevAsync", - 0, + *cursor_id as i32, 0, 0, OwnedValue::build_text(""), 0, "".to_string(), ), - Insn::PrevAwait { .. } => ( + Insn::PrevAwait { cursor_id, .. } => ( "PrevAwait", - 0, + *cursor_id as i32, 0, 0, OwnedValue::build_text(""), diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 7fffb9b22..c02c78d6e 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -501,6 +501,30 @@ pub enum Insn { /// The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end. /// If the P1 index entry is greater or equal than the key value then jump to P2. Otherwise fall through to the next instruction. + // If cursor_id refers to an SQL table (B-Tree that uses integer keys), use the value in start_reg as the key. + // If cursor_id refers to an SQL index, then start_reg is the first in an array of num_regs registers that are used as an unpacked index key. + // Seek to the first index entry that is less than or equal to the given key. If not found, jump to the given PC. Otherwise, continue to the next instruction. + SeekLE { + is_index: bool, + cursor_id: CursorID, + start_reg: usize, + num_regs: usize, + target_pc: BranchOffset, + }, + + // If cursor_id refers to an SQL table (B-Tree that uses integer keys), use the value in start_reg as the key. + // If cursor_id refers to an SQL index, then start_reg is the first in an array of num_regs registers that are used as an unpacked index key. + // Seek to the first index entry that is less than the given key. If not found, jump to the given PC. Otherwise, continue to the next instruction. + SeekLT { + is_index: bool, + cursor_id: CursorID, + start_reg: usize, + num_regs: usize, + target_pc: BranchOffset, + }, + + // The P4 register values beginning with P3 form an unpacked index key that omits the PRIMARY KEY. Compare this key value against the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID fields at the end. + // If the P1 index entry is greater or equal than the key value then jump to P2. Otherwise fall through to the next instruction. IdxGE { cursor_id: CursorID, start_reg: usize, @@ -1306,8 +1330,10 @@ impl Insn { Insn::SeekRowid { .. } => execute::op_seek_rowid, Insn::DeferredSeek { .. } => execute::op_deferred_seek, - Insn::SeekGE { .. } => execute::op_seek_ge, - Insn::SeekGT { .. } => execute::op_seek_gt, + Insn::SeekGE { .. } => execute::op_seek, + Insn::SeekGT { .. } => execute::op_seek, + Insn::SeekLE { .. } => execute::op_seek, + Insn::SeekLT { .. } => execute::op_seek, Insn::SeekEnd { .. } => execute::op_seek_end, Insn::IdxGE { .. } => execute::op_idx_ge, Insn::IdxGT { .. } => execute::op_idx_gt, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 45e656032..cf6918304 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -561,7 +561,10 @@ fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&In | Insn::LastAwait { .. } | Insn::SorterSort { .. } | Insn::SeekGE { .. } - | Insn::SeekGT { .. } => indent_count + 1, + | Insn::SeekGT { .. } + | Insn::SeekLE { .. } + | Insn::SeekLT { .. } => indent_count + 1, + _ => indent_count, } } else {