From 4bd1582e7d0fe72f1c85f83f9a6cd7b60c0e4dd6 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Wed, 30 Jul 2025 20:44:54 -0500 Subject: [PATCH] Implement the Cast opcode 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. --- COMPAT.md | 6 +----- core/translate/expr.rs | 25 ++++++------------------- core/vdbe/execute.rs | 25 +++++++++++++++++++++++++ core/vdbe/explain.rs | 9 +++++++++ core/vdbe/insn.rs | 7 +++++++ 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index a436e80ef..2cd183ced 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -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 | | diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 2d7cdc26b..59cd4945a 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -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) } diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 9a6cbe9db..75b2ff128 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -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, + _mv_store: Option<&Rc>, +) -> Result { + 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 { match self { diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 1578c2d44..e022b675c 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -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} {}", diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 92dc66a15..b801caeb8 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -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,