diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index ddd15bd55..30a0fa26c 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -2392,12 +2392,32 @@ pub fn op_deferred_seek( Ok(InsnFunctionStepResult::Step) } +pub enum OpSeekState { + Start, + Seek { rowid: i64, op: SeekOp }, + MoveLast, +} + pub fn op_seek( program: &Program, state: &mut ProgramState, insn: &Insn, pager: &Rc, mv_store: Option<&Rc>, +) -> Result { + let result = op_seek_internal(program, state, insn, pager, mv_store); + if let Err(_) | Ok(InsnFunctionStepResult::Step) = &result { + state.op_seek_state = OpSeekState::Start; + } + result +} + +pub fn op_seek_internal( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Rc, + mv_store: Option<&Rc>, ) -> Result { let (Insn::SeekGE { cursor_id, @@ -2447,12 +2467,7 @@ pub fn op_seek( 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", - }; + // todo (sivukhin): fix index too if *is_index { let found = { let mut cursor = state.get_cursor(*cursor_id); @@ -2465,102 +2480,116 @@ pub fn op_seek( } else { state.pc += 1; } - } else { - let pc = { - let original_value = state.registers[*start_reg].get_owned_value().clone(); - let mut temp_value = original_value.clone(); - - let conversion_successful = if matches!(temp_value, Value::Text(_)) { - let mut temp_reg = Register::Value(temp_value); - let converted = apply_numeric_affinity(&mut temp_reg, false); - temp_value = temp_reg.get_owned_value().clone(); - converted - } else { - true // Non-text values don't need conversion - }; - - let int_key = extract_int_value(&temp_value); - let lost_precision = !conversion_successful || !matches!(temp_value, Value::Integer(_)); - let actual_op = if lost_precision { - match &temp_value { - Value::Float(f) => { - let int_key_as_float = int_key as f64; - let c = if int_key_as_float > *f { - 1 - } else if int_key_as_float < *f { - -1 - } else { - 0 - }; - - match c.cmp(&0) { - std::cmp::Ordering::Less => match op { - SeekOp::LT => SeekOp::LE { eq_only: false }, // (x < 5.1) -> (x <= 5) - SeekOp::GE { .. } => SeekOp::GT, // (x >= 5.1) -> (x > 5) - other => other, - }, - std::cmp::Ordering::Greater => match op { - SeekOp::GT => SeekOp::GE { eq_only: false }, // (x > 4.9) -> (x >= 5) - SeekOp::LE { .. } => SeekOp::LT, // (x <= 4.9) -> (x < 5) - other => other, - }, - std::cmp::Ordering::Equal => op, - } - } - Value::Text(_) | Value::Blob(_) => { - match op { - SeekOp::GT | SeekOp::GE { .. } => { - // No integers are > or >= non-numeric text, jump to target (empty result) - state.pc = target_pc.as_offset_int(); - return Ok(InsnFunctionStepResult::Step); - } - SeekOp::LT | SeekOp::LE { .. } => { - // All integers are < or <= non-numeric text - // Move to last position and then use the normal seek logic - { - let mut cursor = state.get_cursor(*cursor_id); - let cursor = cursor.as_btree_mut(); - return_if_io!(cursor.last()); - } - state.pc += 1; - return Ok(InsnFunctionStepResult::Step); - } - } - } - _ => op, - } - } else { - op - }; - - let rowid = if matches!(original_value, Value::Null) { - match actual_op { - SeekOp::GE { .. } | SeekOp::GT => { - state.pc = target_pc.as_offset_int(); - return Ok(InsnFunctionStepResult::Step); - } - SeekOp::LE { .. } | SeekOp::LT => { - // No integers are < NULL, so jump to target - state.pc = target_pc.as_offset_int(); - return Ok(InsnFunctionStepResult::Step); - } - } - } else { - int_key - }; - let mut cursor = state.get_cursor(*cursor_id); - let cursor = cursor.as_btree_mut(); - let found = return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), actual_op)); - - if !found { - target_pc.as_offset_int() - } else { - state.pc + 1 - } - }; - state.pc = pc; + return Ok(InsnFunctionStepResult::Step); + } + loop { + match &state.op_seek_state { + OpSeekState::Start => { + let original_value = state.registers[*start_reg].get_owned_value().clone(); + let mut temp_value = original_value.clone(); + + let conversion_successful = if matches!(temp_value, Value::Text(_)) { + let mut temp_reg = Register::Value(temp_value); + let converted = apply_numeric_affinity(&mut temp_reg, false); + temp_value = temp_reg.get_owned_value().clone(); + converted + } else { + true // Non-text values don't need conversion + }; + let int_key = extract_int_value(&temp_value); + let lost_precision = + !conversion_successful || !matches!(temp_value, Value::Integer(_)); + let actual_op = if lost_precision { + match &temp_value { + Value::Float(f) => { + let int_key_as_float = int_key as f64; + let c = if int_key_as_float > *f { + 1 + } else if int_key_as_float < *f { + -1 + } else { + 0 + }; + + match c.cmp(&0) { + std::cmp::Ordering::Less => match op { + SeekOp::LT => SeekOp::LE { eq_only: false }, // (x < 5.1) -> (x <= 5) + SeekOp::GE { .. } => SeekOp::GT, // (x >= 5.1) -> (x > 5) + other => other, + }, + std::cmp::Ordering::Greater => match op { + SeekOp::GT => SeekOp::GE { eq_only: false }, // (x > 4.9) -> (x >= 5) + SeekOp::LE { .. } => SeekOp::LT, // (x <= 4.9) -> (x < 5) + other => other, + }, + std::cmp::Ordering::Equal => op, + } + } + Value::Text(_) | Value::Blob(_) => { + match op { + SeekOp::GT | SeekOp::GE { .. } => { + // No integers are > or >= non-numeric text, jump to target (empty result) + state.pc = target_pc.as_offset_int(); + return Ok(InsnFunctionStepResult::Step); + } + SeekOp::LT | SeekOp::LE { .. } => { + // All integers are < or <= non-numeric text + // Move to last position and then use the normal seek logic + state.op_seek_state = OpSeekState::MoveLast; + continue; + } + } + } + _ => op, + } + } else { + op + }; + let rowid = if matches!(original_value, Value::Null) { + match actual_op { + SeekOp::GE { .. } | SeekOp::GT => { + state.pc = target_pc.as_offset_int(); + return Ok(InsnFunctionStepResult::Step); + } + SeekOp::LE { .. } | SeekOp::LT => { + // No integers are < NULL, so jump to target + state.pc = target_pc.as_offset_int(); + return Ok(InsnFunctionStepResult::Step); + } + } + } else { + int_key + }; + state.op_seek_state = OpSeekState::Seek { + rowid, + op: actual_op, + }; + continue; + } + OpSeekState::Seek { rowid, op } => { + let found = { + let mut cursor = state.get_cursor(*cursor_id); + let cursor = cursor.as_btree_mut(); + return_if_io!(cursor.seek(SeekKey::TableRowId(*rowid), *op)) + }; + if !found { + state.pc = target_pc.as_offset_int() + } else { + state.pc += 1 + } + return Ok(InsnFunctionStepResult::Step); + } + OpSeekState::MoveLast => { + { + let mut cursor = state.get_cursor(*cursor_id); + let cursor = cursor.as_btree_mut(); + return_if_io!(cursor.last()); + } + state.pc += 1; + return Ok(InsnFunctionStepResult::Step); + } + } } - Ok(InsnFunctionStepResult::Step) } /// Returns the tie-breaker ordering for SQLite index comparison opcodes. diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 338f97646..d79e88845 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -33,6 +33,7 @@ use crate::{ vdbe::execute::OpIdxInsertState, vdbe::execute::OpInsertState, vdbe::execute::OpNewRowidState, + vdbe::execute::OpSeekState, RefValue, }; @@ -257,6 +258,7 @@ pub struct ProgramState { op_new_rowid_state: OpNewRowidState, op_idx_insert_state: OpIdxInsertState, op_insert_state: OpInsertState, + op_seek_state: OpSeekState, } impl ProgramState { @@ -286,6 +288,7 @@ impl ProgramState { op_new_rowid_state: OpNewRowidState::Start, op_idx_insert_state: OpIdxInsertState::SeekIfUnique, op_insert_state: OpInsertState::Insert, + op_seek_state: OpSeekState::Start, } }