Merge 'Implement the Cast opcode' from Glauber Costa

Our compat matrix mentions a couple of opcodes: ToInt, ToBlob, etc.
Those opcodes do not exist.
Instead, there is a single Cast opcode, that takes the affinity as a
parameter.
Currently we just call a function when we need to cast. This PR fixes
the compat file, implements the cast opcode, and in at least one
instance, when explicitly using the CAST keyword, uses that opcode
instead of a function in the generated bytecode.

Reviewed-by: Preston Thorpe (@PThorpe92)
Reviewed-by: Preston Thorpe (@PThorpe92)

Closes #2352
This commit is contained in:
PThorpe92
2025-07-30 22:32:09 -04:00
5 changed files with 48 additions and 24 deletions

View File

@@ -427,6 +427,7 @@ Modifiers:
| BitOr | Yes | |
| Blob | Yes | |
| BeginSubrtn | Yes | |
| Cast | Yes | |
| Checkpoint | Yes | |
| Clear | No | |
| Close | Yes | |
@@ -554,11 +555,6 @@ Modifiers:
| String8 | Yes | |
| Subtract | Yes | |
| TableLock | No | |
| ToBlob | No | |
| ToInt | No | |
| ToNumeric | No | |
| ToReal | No | |
| ToText | No | |
| Trace | No | |
| Transaction | Yes | |
| VBegin | No | |

View File

@@ -8,7 +8,7 @@ use super::plan::TableReferences;
use crate::function::JsonFunc;
use crate::function::{Func, FuncCtx, MathFuncArity, ScalarFunc, VectorFunc};
use crate::functions::datetime;
use crate::schema::{Affinity, Table, Type};
use crate::schema::{affinity, Affinity, Table, Type};
use crate::util::{exprs_are_equivalent, parse_numeric_literal};
use crate::vdbe::builder::CursorKey;
use crate::vdbe::{
@@ -651,24 +651,11 @@ pub fn translate_expr(
}
ast::Expr::Cast { expr, type_name } => {
let type_name = type_name.as_ref().unwrap(); // TODO: why is this optional?
let reg_expr = program.alloc_registers(2);
translate_expr(program, referenced_tables, expr, reg_expr, resolver)?;
program.emit_insn(Insn::String8 {
// we make a comparison against uppercase static strs in the affinity() function,
// so we need to make sure we're comparing against the uppercase version,
// and it's better to do this once instead of every time we check affinity
value: type_name.name.to_uppercase(),
dest: reg_expr + 1,
});
program.mark_last_insn_constant();
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: reg_expr,
dest: target_register,
func: FuncCtx {
func: Func::Scalar(ScalarFunc::Cast),
arg_count: 2,
},
translate_expr(program, referenced_tables, expr, target_register, resolver)?;
let type_affinity = affinity(&type_name.name.to_uppercase());
program.emit_insn(Insn::Cast {
reg: target_register,
affinity: type_affinity,
});
Ok(target_register)
}

View File

@@ -6651,6 +6651,31 @@ pub fn op_integrity_check(
Ok(InsnFunctionStepResult::Step)
}
pub fn op_cast(
_program: &Program,
state: &mut ProgramState,
insn: &Insn,
_pager: &Rc<Pager>,
_mv_store: Option<&Rc<MvStore>>,
) -> Result<InsnFunctionStepResult> {
let Insn::Cast { reg, affinity } = insn else {
unreachable!("unexpected Insn {:?}", insn)
};
let value = state.registers[*reg].get_owned_value().clone();
let result = match affinity {
Affinity::Blob => value.exec_cast("BLOB"),
Affinity::Text => value.exec_cast("TEXT"),
Affinity::Numeric => value.exec_cast("NUMERIC"),
Affinity::Integer => value.exec_cast("INTEGER"),
Affinity::Real => value.exec_cast("REAL"),
};
state.registers[*reg] = Register::Value(result);
state.pc += 1;
Ok(InsnFunctionStepResult::Step)
}
impl Value {
pub fn exec_lower(&self) -> Option<Self> {
match self {

View File

@@ -1609,6 +1609,15 @@ pub fn insn_to_str(
0,
format!("r[{}] = data", *dest),
),
Insn::Cast { reg, affinity } => (
"Cast",
*reg as i32,
0,
0,
Value::build_text(""),
0,
format!("affinity(r[{}]={:?})", *reg, affinity),
),
};
format!(
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",

View File

@@ -706,6 +706,12 @@ pub enum Insn {
func: FuncCtx, // P4
},
/// Cast register P1 to affinity P2 and store in register P1
Cast {
reg: usize,
affinity: Affinity,
},
InitCoroutine {
yield_reg: usize,
jump_on_definition: BranchOffset,
@@ -1075,6 +1081,7 @@ impl Insn {
Insn::SorterData { .. } => execute::op_sorter_data,
Insn::SorterNext { .. } => execute::op_sorter_next,
Insn::Function { .. } => execute::op_function,
Insn::Cast { .. } => execute::op_cast,
Insn::InitCoroutine { .. } => execute::op_init_coroutine,
Insn::EndCoroutine { .. } => execute::op_end_coroutine,
Insn::Yield { .. } => execute::op_yield,