limbo/core/translate/expr.rs
Jussi Saurio 913367409e Merge 'Parse hex integers 2' from Anton Harniakou
Continuation of #1329

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #1347
2025-04-16 11:13:01 +03:00

2449 lines
102 KiB
Rust

use limbo_sqlite3_parser::ast::{self, UnaryOperator};
#[cfg(feature = "json")]
use crate::function::JsonFunc;
use crate::function::{Func, FuncCtx, MathFuncArity, ScalarFunc, VectorFunc};
use crate::schema::{Table, Type};
use crate::util::{exprs_are_equivalent, normalize_ident};
use crate::vdbe::{
builder::ProgramBuilder,
insn::{CmpInsFlags, Insn},
BranchOffset,
};
use crate::Result;
use super::emitter::Resolver;
use super::plan::{Operation, TableReference};
#[derive(Debug, Clone, Copy)]
pub struct ConditionMetadata {
pub jump_if_condition_is_true: bool,
pub jump_target_when_true: BranchOffset,
pub jump_target_when_false: BranchOffset,
}
fn emit_cond_jump(program: &mut ProgramBuilder, cond_meta: ConditionMetadata, reg: usize) {
if cond_meta.jump_if_condition_is_true {
program.emit_insn(Insn::If {
reg,
target_pc: cond_meta.jump_target_when_true,
jump_if_null: false,
});
} else {
program.emit_insn(Insn::IfNot {
reg,
target_pc: cond_meta.jump_target_when_false,
jump_if_null: true,
});
}
}
macro_rules! emit_cmp_insn {
(
$program:expr,
$cond:expr,
$op_true:ident,
$op_false:ident,
$lhs:expr,
$rhs:expr
) => {{
if $cond.jump_if_condition_is_true {
$program.emit_insn(Insn::$op_true {
lhs: $lhs,
rhs: $rhs,
target_pc: $cond.jump_target_when_true,
flags: CmpInsFlags::default(),
});
} else {
$program.emit_insn(Insn::$op_false {
lhs: $lhs,
rhs: $rhs,
target_pc: $cond.jump_target_when_false,
flags: CmpInsFlags::default().jump_if_null(),
});
}
}};
}
macro_rules! emit_cmp_null_insn {
(
$program:expr,
$cond:expr,
$op_true:ident,
$op_false:ident,
$lhs:expr,
$rhs:expr
) => {{
if $cond.jump_if_condition_is_true {
$program.emit_insn(Insn::$op_true {
lhs: $lhs,
rhs: $rhs,
target_pc: $cond.jump_target_when_true,
flags: CmpInsFlags::default().null_eq(),
});
} else {
$program.emit_insn(Insn::$op_false {
lhs: $lhs,
rhs: $rhs,
target_pc: $cond.jump_target_when_false,
flags: CmpInsFlags::default().null_eq(),
});
}
}};
}
macro_rules! expect_arguments_exact {
(
$args:expr,
$expected_arguments:expr,
$func:ident
) => {{
let args = if let Some(args) = $args {
if args.len() != $expected_arguments {
crate::bail_parse_error!(
"{} function called with not exactly {} arguments",
$func.to_string(),
$expected_arguments,
);
}
args
} else {
crate::bail_parse_error!("{} function with no arguments", $func.to_string());
};
args
}};
}
macro_rules! expect_arguments_max {
(
$args:expr,
$expected_arguments:expr,
$func:ident
) => {{
let args = if let Some(args) = $args {
if args.len() > $expected_arguments {
crate::bail_parse_error!(
"{} function called with more than {} arguments",
$func.to_string(),
$expected_arguments,
);
}
args
} else {
crate::bail_parse_error!("{} function with no arguments", $func.to_string());
};
args
}};
}
macro_rules! expect_arguments_min {
(
$args:expr,
$expected_arguments:expr,
$func:ident
) => {{
let args = if let Some(args) = $args {
if args.len() < $expected_arguments {
crate::bail_parse_error!(
"{} function with less than {} arguments",
$func.to_string(),
$expected_arguments
);
}
args
} else {
crate::bail_parse_error!("{} function with no arguments", $func.to_string());
};
args
}};
}
#[allow(unused_macros)]
macro_rules! expect_arguments_even {
(
$args:expr,
$func:ident
) => {{
let args = $args.as_deref().unwrap_or_default();
if args.len() % 2 != 0 {
crate::bail_parse_error!(
"{} function requires an even number of arguments",
$func.to_string()
);
};
// The only function right now that requires an even number is `json_object` and it allows
// to have no arguments, so thats why in this macro we do not bail with the `function with no arguments` error
args
}};
}
pub fn translate_condition_expr(
program: &mut ProgramBuilder,
referenced_tables: &[TableReference],
expr: &ast::Expr,
condition_metadata: ConditionMetadata,
resolver: &Resolver,
) -> Result<()> {
match expr {
ast::Expr::Between { .. } => todo!(),
ast::Expr::Binary(lhs, ast::Operator::And, rhs) => {
// In a binary AND, never jump to the parent 'jump_target_when_true' label on the first condition, because
// the second condition MUST also be true. Instead we instruct the child expression to jump to a local
// true label.
let jump_target_when_true = program.allocate_label();
translate_condition_expr(
program,
referenced_tables,
lhs,
ConditionMetadata {
jump_if_condition_is_true: false,
jump_target_when_true,
..condition_metadata
},
resolver,
)?;
program.resolve_label(jump_target_when_true, program.offset());
translate_condition_expr(
program,
referenced_tables,
rhs,
condition_metadata,
resolver,
)?;
}
ast::Expr::Binary(lhs, ast::Operator::Or, rhs) => {
// In a binary OR, never jump to the parent 'jump_target_when_false' label on the first condition, because
// the second condition CAN also be true. Instead we instruct the child expression to jump to a local
// false label.
let jump_target_when_false = program.allocate_label();
translate_condition_expr(
program,
referenced_tables,
lhs,
ConditionMetadata {
jump_if_condition_is_true: true,
jump_target_when_false,
..condition_metadata
},
resolver,
)?;
program.resolve_label(jump_target_when_false, program.offset());
translate_condition_expr(
program,
referenced_tables,
rhs,
condition_metadata,
resolver,
)?;
}
ast::Expr::Binary(lhs, op, rhs)
if matches!(
op,
ast::Operator::Greater
| ast::Operator::GreaterEquals
| ast::Operator::Less
| ast::Operator::LessEquals
| ast::Operator::Equals
| ast::Operator::NotEquals
| ast::Operator::Is
| ast::Operator::IsNot
) =>
{
let lhs_reg = program.alloc_register();
let rhs_reg = program.alloc_register();
translate_and_mark(program, Some(referenced_tables), lhs, lhs_reg, resolver)?;
translate_and_mark(program, Some(referenced_tables), rhs, rhs_reg, resolver)?;
match op {
ast::Operator::Greater => {
emit_cmp_insn!(program, condition_metadata, Gt, Le, lhs_reg, rhs_reg)
}
ast::Operator::GreaterEquals => {
emit_cmp_insn!(program, condition_metadata, Ge, Lt, lhs_reg, rhs_reg)
}
ast::Operator::Less => {
emit_cmp_insn!(program, condition_metadata, Lt, Ge, lhs_reg, rhs_reg)
}
ast::Operator::LessEquals => {
emit_cmp_insn!(program, condition_metadata, Le, Gt, lhs_reg, rhs_reg)
}
ast::Operator::Equals => {
emit_cmp_insn!(program, condition_metadata, Eq, Ne, lhs_reg, rhs_reg)
}
ast::Operator::NotEquals => {
emit_cmp_insn!(program, condition_metadata, Ne, Eq, lhs_reg, rhs_reg)
}
ast::Operator::Is => {
emit_cmp_null_insn!(program, condition_metadata, Eq, Ne, lhs_reg, rhs_reg)
}
ast::Operator::IsNot => {
emit_cmp_null_insn!(program, condition_metadata, Ne, Eq, lhs_reg, rhs_reg)
}
_ => unreachable!(),
}
}
ast::Expr::Binary(_, _, _) => {
let result_reg = program.alloc_register();
translate_expr(program, Some(referenced_tables), expr, result_reg, resolver)?;
emit_cond_jump(program, condition_metadata, result_reg);
}
ast::Expr::Literal(_)
| ast::Expr::Cast { .. }
| ast::Expr::FunctionCall { .. }
| ast::Expr::Column { .. }
| ast::Expr::RowId { .. }
| ast::Expr::Case { .. } => {
let reg = program.alloc_register();
translate_expr(program, Some(referenced_tables), expr, reg, resolver)?;
emit_cond_jump(program, condition_metadata, reg);
}
ast::Expr::InList { lhs, not, rhs } => {
// lhs is e.g. a column reference
// rhs is an Option<Vec<Expr>>
// If rhs is None, it means the IN expression is always false, i.e. tbl.id IN ().
// If rhs is Some, it means the IN expression has a list of values to compare against, e.g. tbl.id IN (1, 2, 3).
//
// The IN expression is equivalent to a series of OR expressions.
// For example, `a IN (1, 2, 3)` is equivalent to `a = 1 OR a = 2 OR a = 3`.
// The NOT IN expression is equivalent to a series of AND expressions.
// For example, `a NOT IN (1, 2, 3)` is equivalent to `a != 1 AND a != 2 AND a != 3`.
//
// SQLite typically optimizes IN expressions to use a binary search on an ephemeral index if there are many values.
// For now we don't have the plumbing to do that, so we'll just emit a series of comparisons,
// which is what SQLite also does for small lists of values.
// TODO: Let's refactor this later to use a more efficient implementation conditionally based on the number of values.
if rhs.is_none() {
// If rhs is None, IN expressions are always false and NOT IN expressions are always true.
if *not {
// On a trivially true NOT IN () expression we can only jump to the 'jump_target_when_true' label if 'jump_if_condition_is_true'; otherwise me must fall through.
// This is because in a more complex condition we might need to evaluate the rest of the condition.
// Note that we are already breaking up our WHERE clauses into a series of terms at "AND" boundaries, so right now we won't be running into cases where jumping on true would be incorrect,
// but once we have e.g. parenthesization and more complex conditions, not having this 'if' here would introduce a bug.
if condition_metadata.jump_if_condition_is_true {
program.emit_insn(Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
});
}
} else {
program.emit_insn(Insn::Goto {
target_pc: condition_metadata.jump_target_when_false,
});
}
return Ok(());
}
// The left hand side only needs to be evaluated once we have a list of values to compare against.
let lhs_reg = program.alloc_register();
let _ = translate_expr(program, Some(referenced_tables), lhs, lhs_reg, resolver)?;
let rhs = rhs.as_ref().unwrap();
// The difference between a local jump and an "upper level" jump is that for example in this case:
// WHERE foo IN (1,2,3) OR bar = 5,
// we can immediately jump to the 'jump_target_when_true' label of the ENTIRE CONDITION if foo = 1, foo = 2, or foo = 3 without evaluating the bar = 5 condition.
// This is why in Binary-OR expressions we set jump_if_condition_is_true to true for the first condition.
// However, in this example:
// WHERE foo IN (1,2,3) AND bar = 5,
// we can't jump to the 'jump_target_when_true' label of the entire condition foo = 1, foo = 2, or foo = 3, because we still need to evaluate the bar = 5 condition later.
// This is why in that case we just jump over the rest of the IN conditions in this "local" branch which evaluates the IN condition.
let jump_target_when_true = if condition_metadata.jump_if_condition_is_true {
condition_metadata.jump_target_when_true
} else {
program.allocate_label()
};
if !*not {
// If it's an IN expression, we need to jump to the 'jump_target_when_true' label if any of the conditions are true.
for (i, expr) in rhs.iter().enumerate() {
let rhs_reg = program.alloc_register();
let last_condition = i == rhs.len() - 1;
let _ =
translate_expr(program, Some(referenced_tables), expr, rhs_reg, resolver)?;
// If this is not the last condition, we need to jump to the 'jump_target_when_true' label if the condition is true.
if !last_condition {
program.emit_insn(Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: jump_target_when_true,
flags: CmpInsFlags::default(),
});
} else {
// If this is the last condition, we need to jump to the 'jump_target_when_false' label if there is no match.
program.emit_insn(Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
flags: CmpInsFlags::default().jump_if_null(),
});
}
}
// If we got here, then the last condition was a match, so we jump to the 'jump_target_when_true' label if 'jump_if_condition_is_true'.
// If not, we can just fall through without emitting an unnecessary instruction.
if condition_metadata.jump_if_condition_is_true {
program.emit_insn(Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
});
}
} else {
// If it's a NOT IN expression, we need to jump to the 'jump_target_when_false' label if any of the conditions are true.
for expr in rhs.iter() {
let rhs_reg = program.alloc_register();
let _ =
translate_expr(program, Some(referenced_tables), expr, rhs_reg, resolver)?;
program.emit_insn(Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
flags: CmpInsFlags::default().jump_if_null(),
});
}
// If we got here, then none of the conditions were a match, so we jump to the 'jump_target_when_true' label if 'jump_if_condition_is_true'.
// If not, we can just fall through without emitting an unnecessary instruction.
if condition_metadata.jump_if_condition_is_true {
program.emit_insn(Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
});
}
}
if !condition_metadata.jump_if_condition_is_true {
program.resolve_label(jump_target_when_true, program.offset());
}
}
ast::Expr::Like { not, .. } => {
let cur_reg = program.alloc_register();
translate_like_base(program, Some(referenced_tables), expr, cur_reg, resolver)?;
if !*not {
emit_cond_jump(program, condition_metadata, cur_reg);
} else if condition_metadata.jump_if_condition_is_true {
program.emit_insn(Insn::IfNot {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_true,
jump_if_null: false,
});
} else {
program.emit_insn(Insn::If {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_false,
jump_if_null: true,
});
}
}
ast::Expr::Parenthesized(exprs) => {
if exprs.len() == 1 {
let _ = translate_condition_expr(
program,
referenced_tables,
&exprs[0],
condition_metadata,
resolver,
);
} else {
crate::bail_parse_error!(
"parenthesized conditional should have exactly one expression"
);
}
}
ast::Expr::NotNull(expr) => {
let cur_reg = program.alloc_register();
translate_expr(program, Some(referenced_tables), expr, cur_reg, resolver)?;
program.emit_insn(Insn::IsNull {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_false,
});
}
ast::Expr::IsNull(expr) => {
let cur_reg = program.alloc_register();
translate_expr(program, Some(referenced_tables), expr, cur_reg, resolver)?;
program.emit_insn(Insn::NotNull {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_false,
});
}
ast::Expr::Unary(_, _) => {
// This is an inefficient implementation for op::NOT, because translate_expr() will emit an Insn::Not,
// and then we immediately emit an Insn::If/Insn::IfNot for the conditional jump. In reality we would not
// like to emit the negation instruction Insn::Not at all, since we could just emit the "opposite" jump instruction
// directly. However, using translate_expr() directly simplifies our conditional jump code for unary expressions,
// and we'd rather be correct than maximally efficient, for now.
let expr_reg = program.alloc_register();
translate_expr(program, Some(referenced_tables), expr, expr_reg, resolver)?;
emit_cond_jump(program, condition_metadata, expr_reg);
}
other => todo!("expression {:?} not implemented", other),
}
Ok(())
}
pub fn translate_expr(
program: &mut ProgramBuilder,
referenced_tables: Option<&[TableReference]>,
expr: &ast::Expr,
target_register: usize,
resolver: &Resolver,
) -> Result<usize> {
if let Some(reg) = resolver.resolve_cached_expr_reg(expr) {
program.emit_insn(Insn::Copy {
src_reg: reg,
dst_reg: target_register,
amount: 0,
});
return Ok(target_register);
}
match expr {
ast::Expr::Between { .. } => todo!(),
ast::Expr::Binary(e1, op, e2) => {
// Check if both sides of the expression are equivalent and reuse the same register if so
if exprs_are_equivalent(e1, e2) {
let shared_reg = program.alloc_register();
translate_expr(program, referenced_tables, e1, shared_reg, resolver)?;
emit_binary_insn(program, op, shared_reg, shared_reg, target_register)?;
return Ok(target_register);
}
let e1_reg = program.alloc_registers(2);
let e2_reg = e1_reg + 1;
translate_expr(program, referenced_tables, e1, e1_reg, resolver)?;
translate_expr(program, referenced_tables, e2, e2_reg, resolver)?;
emit_binary_insn(program, op, e1_reg, e2_reg, target_register)?;
Ok(target_register)
}
ast::Expr::Case {
base,
when_then_pairs,
else_expr,
} => {
// There's two forms of CASE, one which checks a base expression for equality
// against the WHEN values, and returns the corresponding THEN value if it matches:
// CASE 2 WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'many' END
// And one which evaluates a series of boolean predicates:
// CASE WHEN is_good THEN 'good' WHEN is_bad THEN 'bad' ELSE 'okay' END
// This just changes which sort of branching instruction to issue, after we
// generate the expression if needed.
let return_label = program.allocate_label();
let mut next_case_label = program.allocate_label();
// Only allocate a reg to hold the base expression if one was provided.
// And base_reg then becomes the flag we check to see which sort of
// case statement we're processing.
let base_reg = base.as_ref().map(|_| program.alloc_register());
let expr_reg = program.alloc_register();
if let Some(base_expr) = base {
translate_expr(
program,
referenced_tables,
base_expr,
base_reg.unwrap(),
resolver,
)?;
};
for (when_expr, then_expr) in when_then_pairs {
translate_expr(program, referenced_tables, when_expr, expr_reg, resolver)?;
match base_reg {
// CASE 1 WHEN 0 THEN 0 ELSE 1 becomes 1==0, Ne branch to next clause
Some(base_reg) => program.emit_insn(Insn::Ne {
lhs: base_reg,
rhs: expr_reg,
target_pc: next_case_label,
// A NULL result is considered untrue when evaluating WHEN terms.
flags: CmpInsFlags::default().jump_if_null(),
}),
// CASE WHEN 0 THEN 0 ELSE 1 becomes ifnot 0 branch to next clause
None => program.emit_insn(Insn::IfNot {
reg: expr_reg,
target_pc: next_case_label,
jump_if_null: true,
}),
};
// THEN...
translate_expr(
program,
referenced_tables,
then_expr,
target_register,
resolver,
)?;
program.emit_insn(Insn::Goto {
target_pc: return_label,
});
// This becomes either the next WHEN, or in the last WHEN/THEN, we're
// assured to have at least one instruction corresponding to the ELSE immediately follow.
program.preassign_label_to_next_insn(next_case_label);
next_case_label = program.allocate_label();
}
match else_expr {
Some(expr) => {
translate_expr(program, referenced_tables, expr, target_register, resolver)?;
}
// If ELSE isn't specified, it means ELSE null.
None => {
program.emit_insn(Insn::Null {
dest: target_register,
dest_end: None,
});
}
};
program.resolve_label(return_label, program.offset());
Ok(target_register)
}
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,
},
});
Ok(target_register)
}
ast::Expr::Collate(_, _) => todo!(),
ast::Expr::DoublyQualified(_, _, _) => todo!(),
ast::Expr::Exists(_) => todo!(),
ast::Expr::FunctionCall {
name,
distinctness: _,
args,
filter_over: _,
order_by: _,
} => {
let args_count = if let Some(args) = args { args.len() } else { 0 };
let func_name = normalize_ident(name.0.as_str());
let func_type = resolver.resolve_function(&func_name, args_count);
if func_type.is_none() {
crate::bail_parse_error!("unknown function {}", name.0);
}
let func_ctx = FuncCtx {
func: func_type.unwrap(),
arg_count: args_count,
};
match &func_ctx.func {
Func::Agg(_) => {
crate::bail_parse_error!("aggregation function in non-aggregation context")
}
Func::External(_) => {
let regs = program.alloc_registers(args_count);
if let Some(args) = args {
for (i, arg_expr) in args.iter().enumerate() {
translate_expr(
program,
referenced_tables,
arg_expr,
regs + i,
resolver,
)?;
}
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: regs,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
#[cfg(feature = "json")]
Func::Json(j) => match j {
JsonFunc::Json | JsonFunc::Jsonb => {
let args = expect_arguments_exact!(args, 1, j);
translate_function(
program,
args,
referenced_tables,
resolver,
target_register,
func_ctx,
)
}
JsonFunc::JsonArray
| JsonFunc::JsonbArray
| JsonFunc::JsonExtract
| JsonFunc::JsonSet
| JsonFunc::JsonbSet
| JsonFunc::JsonbExtract
| JsonFunc::JsonReplace
| JsonFunc::JsonbReplace
| JsonFunc::JsonbRemove
| JsonFunc::JsonInsert
| JsonFunc::JsonbInsert => translate_function(
program,
args.as_deref().unwrap_or_default(),
referenced_tables,
resolver,
target_register,
func_ctx,
),
JsonFunc::JsonArrowExtract | JsonFunc::JsonArrowShiftExtract => {
unreachable!(
"These two functions are only reachable via the -> and ->> operators"
)
}
JsonFunc::JsonArrayLength | JsonFunc::JsonType => {
let args = expect_arguments_max!(args, 2, j);
translate_function(
program,
args,
referenced_tables,
resolver,
target_register,
func_ctx,
)
}
JsonFunc::JsonErrorPosition => {
let args = if let Some(args) = args {
if args.len() != 1 {
crate::bail_parse_error!(
"{} function with not exactly 1 argument",
j.to_string()
);
}
args
} else {
crate::bail_parse_error!(
"{} function with no arguments",
j.to_string()
);
};
let json_reg = program.alloc_register();
translate_expr(program, referenced_tables, &args[0], json_reg, resolver)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: json_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
JsonFunc::JsonObject | JsonFunc::JsonbObject => {
let args = expect_arguments_even!(args, j);
translate_function(
program,
args,
referenced_tables,
resolver,
target_register,
func_ctx,
)
}
JsonFunc::JsonValid => translate_function(
program,
args.as_deref().unwrap_or_default(),
referenced_tables,
resolver,
target_register,
func_ctx,
),
JsonFunc::JsonPatch | JsonFunc::JsonbPatch => {
let args = expect_arguments_exact!(args, 2, j);
translate_function(
program,
args,
referenced_tables,
resolver,
target_register,
func_ctx,
)
}
JsonFunc::JsonRemove => {
let start_reg =
program.alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1));
if let Some(args) = args {
for (i, arg) in args.iter().enumerate() {
// register containing result of each argument expression
translate_and_mark(
program,
referenced_tables,
arg,
start_reg + i,
resolver,
)?;
}
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
JsonFunc::JsonQuote => {
let args = expect_arguments_exact!(args, 1, j);
translate_function(
program,
args,
referenced_tables,
resolver,
target_register,
func_ctx,
)
}
JsonFunc::JsonPretty => {
let args = expect_arguments_max!(args, 2, j);
translate_function(
program,
args,
referenced_tables,
resolver,
target_register,
func_ctx,
)
}
},
Func::Vector(vector_func) => match vector_func {
VectorFunc::Vector | VectorFunc::Vector32 => {
let args = expect_arguments_exact!(args, 1, vector_func);
let start_reg = program.alloc_register();
translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
VectorFunc::Vector64 => {
let args = expect_arguments_exact!(args, 1, vector_func);
let start_reg = program.alloc_register();
translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
VectorFunc::VectorExtract => {
let args = expect_arguments_exact!(args, 1, vector_func);
let start_reg = program.alloc_register();
translate_expr(program, referenced_tables, &args[0], start_reg, resolver)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
VectorFunc::VectorDistanceCos => {
let args = expect_arguments_exact!(args, 2, vector_func);
let regs = program.alloc_registers(2);
translate_expr(program, referenced_tables, &args[0], regs, resolver)?;
translate_expr(program, referenced_tables, &args[1], regs + 1, resolver)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: regs,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
},
Func::Scalar(srf) => {
match srf {
ScalarFunc::Cast => {
unreachable!("this is always ast::Expr::Cast")
}
ScalarFunc::Changes => {
if args.is_some() {
crate::bail_parse_error!(
"{} function with more than 0 arguments",
srf
);
}
let start_reg = program.alloc_register();
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Char => translate_function(
program,
args.as_deref().unwrap_or_default(),
referenced_tables,
resolver,
target_register,
func_ctx,
),
ScalarFunc::Coalesce => {
let args = expect_arguments_min!(args, 2, srf);
// coalesce function is implemented as a series of not null checks
// whenever a not null check succeeds, we jump to the end of the series
let label_coalesce_end = program.allocate_label();
for (index, arg) in args.iter().enumerate() {
let reg = translate_expr(
program,
referenced_tables,
arg,
target_register,
resolver,
)?;
if index < args.len() - 1 {
program.emit_insn(Insn::NotNull {
reg,
target_pc: label_coalesce_end,
});
}
}
program.preassign_label_to_next_insn(label_coalesce_end);
Ok(target_register)
}
ScalarFunc::LastInsertRowid => {
let regs = program.alloc_register();
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: regs,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Concat => {
let args = if let Some(args) = args {
args
} else {
crate::bail_parse_error!(
"{} function with no arguments",
srf.to_string()
);
};
let mut start_reg = None;
for arg in args.iter() {
let reg = program.alloc_register();
start_reg = Some(start_reg.unwrap_or(reg));
translate_expr(program, referenced_tables, arg, reg, resolver)?;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: start_reg.unwrap(),
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::ConcatWs => {
let args = expect_arguments_min!(args, 2, srf);
let temp_register = program.alloc_register();
for arg in args.iter() {
let reg = program.alloc_register();
translate_expr(program, referenced_tables, arg, reg, resolver)?;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: temp_register + 1,
dest: temp_register,
func: func_ctx,
});
program.emit_insn(Insn::Copy {
src_reg: temp_register,
dst_reg: target_register,
amount: 1,
});
Ok(target_register)
}
ScalarFunc::IfNull => {
let args = match args {
Some(args) if args.len() == 2 => args,
Some(_) => crate::bail_parse_error!(
"{} function requires exactly 2 arguments",
srf.to_string()
),
None => crate::bail_parse_error!(
"{} function requires arguments",
srf.to_string()
),
};
let temp_reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
temp_reg,
resolver,
)?;
let before_copy_label = program.allocate_label();
program.emit_insn(Insn::NotNull {
reg: temp_reg,
target_pc: before_copy_label,
});
translate_expr(
program,
referenced_tables,
&args[1],
temp_reg,
resolver,
)?;
program.resolve_label(before_copy_label, program.offset());
program.emit_insn(Insn::Copy {
src_reg: temp_reg,
dst_reg: target_register,
amount: 0,
});
Ok(target_register)
}
ScalarFunc::Iif => {
let args = match args {
Some(args) if args.len() == 3 => args,
_ => crate::bail_parse_error!(
"{} requires exactly 3 arguments",
srf.to_string()
),
};
let temp_reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
temp_reg,
resolver,
)?;
let jump_target_when_false = program.allocate_label();
program.emit_insn(Insn::IfNot {
reg: temp_reg,
target_pc: jump_target_when_false,
jump_if_null: true,
});
translate_expr(
program,
referenced_tables,
&args[1],
target_register,
resolver,
)?;
let jump_target_result = program.allocate_label();
program.emit_insn(Insn::Goto {
target_pc: jump_target_result,
});
program.resolve_label(jump_target_when_false, program.offset());
translate_expr(
program,
referenced_tables,
&args[2],
target_register,
resolver,
)?;
program.resolve_label(jump_target_result, program.offset());
Ok(target_register)
}
ScalarFunc::Glob | ScalarFunc::Like => {
let args = if let Some(args) = args {
if args.len() < 2 {
crate::bail_parse_error!(
"{} function with less than 2 arguments",
srf.to_string()
);
}
args
} else {
crate::bail_parse_error!(
"{} function with no arguments",
srf.to_string()
);
};
let func_registers = program.alloc_registers(args.len());
for (i, arg) in args.iter().enumerate() {
let _ = translate_expr(
program,
referenced_tables,
arg,
func_registers + i,
resolver,
)?;
}
program.emit_insn(Insn::Function {
// Only constant patterns for LIKE are supported currently, so this
// is always 1
constant_mask: 1,
start_reg: func_registers,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Abs
| ScalarFunc::Lower
| ScalarFunc::Upper
| ScalarFunc::Length
| ScalarFunc::OctetLength
| ScalarFunc::Typeof
| ScalarFunc::Unicode
| ScalarFunc::Quote
| ScalarFunc::RandomBlob
| ScalarFunc::Sign
| ScalarFunc::Soundex
| ScalarFunc::ZeroBlob => {
let args = expect_arguments_exact!(args, 1, srf);
let start_reg = program.alloc_register();
translate_and_mark(
program,
referenced_tables,
&args[0],
start_reg,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
#[cfg(feature = "fs")]
ScalarFunc::LoadExtension => {
let args = expect_arguments_exact!(args, 1, srf);
let start_reg = program.alloc_register();
translate_and_mark(
program,
referenced_tables,
&args[0],
start_reg,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Random => {
if args.is_some() {
crate::bail_parse_error!(
"{} function with arguments",
srf.to_string()
);
}
let regs = program.alloc_register();
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: regs,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Date | ScalarFunc::DateTime | ScalarFunc::JulianDay => {
let start_reg = program
.alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1));
if let Some(args) = args {
for (i, arg) in args.iter().enumerate() {
// register containing result of each argument expression
translate_and_mark(
program,
referenced_tables,
arg,
start_reg + i,
resolver,
)?;
}
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Substr | ScalarFunc::Substring => {
let args = if let Some(args) = args {
if !(args.len() == 2 || args.len() == 3) {
crate::bail_parse_error!(
"{} function with wrong number of arguments",
srf.to_string()
)
}
args
} else {
crate::bail_parse_error!(
"{} function with no arguments",
srf.to_string()
);
};
let str_reg = program.alloc_register();
let start_reg = program.alloc_register();
let length_reg = program.alloc_register();
let str_reg = translate_expr(
program,
referenced_tables,
&args[0],
str_reg,
resolver,
)?;
let _ = translate_expr(
program,
referenced_tables,
&args[1],
start_reg,
resolver,
)?;
if args.len() == 3 {
translate_expr(
program,
referenced_tables,
&args[2],
length_reg,
resolver,
)?;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: str_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Hex => {
let args = if let Some(args) = args {
if args.len() != 1 {
crate::bail_parse_error!(
"hex function must have exactly 1 argument",
);
}
args
} else {
crate::bail_parse_error!("hex function with no arguments",);
};
let start_reg = program.alloc_register();
translate_and_mark(
program,
referenced_tables,
&args[0],
start_reg,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::UnixEpoch => {
let mut start_reg = 0;
match args {
Some(args) if args.len() > 1 => {
crate::bail_parse_error!("epoch function with > 1 arguments. Modifiers are not yet supported.");
}
Some(args) if args.len() == 1 => {
let arg_reg = program.alloc_register();
let _ = translate_expr(
program,
referenced_tables,
&args[0],
arg_reg,
resolver,
)?;
start_reg = arg_reg;
}
_ => {}
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Time => {
let start_reg = program
.alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1));
if let Some(args) = args {
for (i, arg) in args.iter().enumerate() {
// register containing result of each argument expression
translate_and_mark(
program,
referenced_tables,
arg,
start_reg + i,
resolver,
)?;
}
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::TimeDiff => {
let args = expect_arguments_exact!(args, 2, srf);
let start_reg = program.alloc_registers(2);
translate_expr(
program,
referenced_tables,
&args[0],
start_reg,
resolver,
)?;
translate_expr(
program,
referenced_tables,
&args[1],
start_reg + 1,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::TotalChanges => {
if args.is_some() {
crate::bail_parse_error!(
"{} function with more than 0 arguments",
srf.to_string()
);
}
let start_reg = program.alloc_register();
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Trim
| ScalarFunc::LTrim
| ScalarFunc::RTrim
| ScalarFunc::Round
| ScalarFunc::Unhex => {
let args = expect_arguments_max!(args, 2, srf);
let start_reg = program.alloc_registers(args.len());
for (i, arg) in args.iter().enumerate() {
translate_and_mark(
program,
referenced_tables,
arg,
start_reg + i,
resolver,
)?;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Min => {
let args = if let Some(args) = args {
if args.is_empty() {
crate::bail_parse_error!(
"min function with less than one argument"
);
}
args
} else {
crate::bail_parse_error!("min function with no arguments");
};
let start_reg = program.alloc_registers(args.len());
for (i, arg) in args.iter().enumerate() {
translate_and_mark(
program,
referenced_tables,
arg,
start_reg + i,
resolver,
)?;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Max => {
let args = if let Some(args) = args {
if args.is_empty() {
crate::bail_parse_error!(
"max function with less than one argument"
);
}
args
} else {
crate::bail_parse_error!("max function with no arguments");
};
let start_reg = program.alloc_registers(args.len());
for (i, arg) in args.iter().enumerate() {
translate_and_mark(
program,
referenced_tables,
arg,
start_reg + i,
resolver,
)?;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Nullif | ScalarFunc::Instr => {
let args = if let Some(args) = args {
if args.len() != 2 {
crate::bail_parse_error!(
"{} function must have two argument",
srf.to_string()
);
}
args
} else {
crate::bail_parse_error!(
"{} function with no arguments",
srf.to_string()
);
};
let first_reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
first_reg,
resolver,
)?;
let second_reg = program.alloc_register();
let _ = translate_expr(
program,
referenced_tables,
&args[1],
second_reg,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: first_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::SqliteVersion => {
if args.is_some() {
crate::bail_parse_error!("sqlite_version function with arguments");
}
let output_register = program.alloc_register();
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: output_register,
dest: output_register,
func: func_ctx,
});
program.emit_insn(Insn::Copy {
src_reg: output_register,
dst_reg: target_register,
amount: 0,
});
Ok(target_register)
}
ScalarFunc::SqliteSourceId => {
if args.is_some() {
crate::bail_parse_error!(
"sqlite_source_id function with arguments"
);
}
let output_register = program.alloc_register();
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: output_register,
dest: output_register,
func: func_ctx,
});
program.emit_insn(Insn::Copy {
src_reg: output_register,
dst_reg: target_register,
amount: 0,
});
Ok(target_register)
}
ScalarFunc::Replace => {
let args = if let Some(args) = args {
if !args.len() == 3 {
crate::bail_parse_error!(
"function {}() requires exactly 3 arguments",
srf.to_string()
)
}
args
} else {
crate::bail_parse_error!(
"function {}() requires exactly 3 arguments",
srf.to_string()
);
};
let str_reg = program.alloc_register();
let pattern_reg = program.alloc_register();
let replacement_reg = program.alloc_register();
let _ = translate_expr(
program,
referenced_tables,
&args[0],
str_reg,
resolver,
)?;
let _ = translate_expr(
program,
referenced_tables,
&args[1],
pattern_reg,
resolver,
)?;
let _ = translate_expr(
program,
referenced_tables,
&args[2],
replacement_reg,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: str_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::StrfTime => {
let start_reg = program
.alloc_registers(args.as_ref().map(|x| x.len()).unwrap_or(1));
if let Some(args) = args {
for (i, arg) in args.iter().enumerate() {
// register containing result of each argument expression
translate_and_mark(
program,
referenced_tables,
arg,
start_reg + i,
resolver,
)?;
}
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Printf => translate_function(
program,
args.as_deref().unwrap_or(&[]),
referenced_tables,
resolver,
target_register,
func_ctx,
),
ScalarFunc::Likely => {
let args = if let Some(args) = args {
if args.len() != 1 {
crate::bail_parse_error!(
"likely function must have exactly 1 argument",
);
}
args
} else {
crate::bail_parse_error!("likely function with no arguments",);
};
let start_reg = program.alloc_register();
translate_and_mark(
program,
referenced_tables,
&args[0],
start_reg,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Likelihood => {
let args = if let Some(args) = args {
if args.len() != 2 {
crate::bail_parse_error!(
"likelihood() function must have exactly 2 arguments",
);
}
args
} else {
crate::bail_parse_error!("likelihood() function with no arguments",);
};
if let ast::Expr::Literal(ast::Literal::Numeric(ref value)) = args[1] {
if let Ok(probability) = value.parse::<f64>() {
if !(0.0..=1.0).contains(&probability) {
crate::bail_parse_error!(
"second argument of likelihood() must be between 0.0 and 1.0",
);
}
if !value.contains('.') {
crate::bail_parse_error!(
"second argument of likelihood() must be a floating point number with decimal point",
);
}
} else {
crate::bail_parse_error!(
"second argument of likelihood() must be a floating point constant",
);
}
} else {
crate::bail_parse_error!(
"second argument of likelihood() must be a numeric literal",
);
}
let start_reg = program.alloc_register();
translate_and_mark(
program,
referenced_tables,
&args[0],
start_reg,
resolver,
)?;
program.emit_insn(Insn::Copy {
src_reg: start_reg,
dst_reg: target_register,
amount: 0,
});
Ok(target_register)
}
}
}
Func::Math(math_func) => match math_func.arity() {
MathFuncArity::Nullary => {
if args.is_some() {
crate::bail_parse_error!("{} function with arguments", math_func);
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: 0,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
MathFuncArity::Unary => {
let args = expect_arguments_exact!(args, 1, math_func);
let start_reg = program.alloc_register();
translate_and_mark(
program,
referenced_tables,
&args[0],
start_reg,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
MathFuncArity::Binary => {
let args = expect_arguments_exact!(args, 2, math_func);
let start_reg = program.alloc_registers(2);
let _ = translate_expr(
program,
referenced_tables,
&args[0],
start_reg,
resolver,
)?;
let _ = translate_expr(
program,
referenced_tables,
&args[1],
start_reg + 1,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
MathFuncArity::UnaryOrBinary => {
let args = expect_arguments_max!(args, 2, math_func);
let regs = program.alloc_registers(args.len());
for (i, arg) in args.iter().enumerate() {
translate_expr(program, referenced_tables, arg, regs + i, resolver)?;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: regs,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
},
}
}
ast::Expr::FunctionCallStar { .. } => todo!(),
ast::Expr::Id(id) => crate::bail_parse_error!(
"no such column: {} - should this be a string literal in single-quotes?",
id.0
),
ast::Expr::Column {
database: _,
table,
column,
is_rowid_alias,
} => {
let table_reference = referenced_tables.as_ref().unwrap().get(*table).unwrap();
match table_reference.op {
// If we are reading a column from a table, we find the cursor that corresponds to
// the table and read the column from the cursor.
Operation::Scan { .. } | Operation::Search(_) => match &table_reference.table {
Table::BTree(_) => {
let cursor_id = program.resolve_cursor_id(&table_reference.identifier);
if *is_rowid_alias {
program.emit_insn(Insn::RowId {
cursor_id,
dest: target_register,
});
} else {
program.emit_insn(Insn::Column {
cursor_id,
column: *column,
dest: target_register,
});
}
let Some(column) = table_reference.table.get_column_at(*column) else {
crate::bail_parse_error!("column index out of bounds");
};
maybe_apply_affinity(column.ty, target_register, program);
Ok(target_register)
}
Table::Virtual(_) => {
let cursor_id = program.resolve_cursor_id(&table_reference.identifier);
program.emit_insn(Insn::VColumn {
cursor_id,
column: *column,
dest: target_register,
});
Ok(target_register)
}
_ => unreachable!(),
},
// If we are reading a column from a subquery, we instead copy the column from the
// subquery's result registers.
Operation::Subquery {
result_columns_start_reg,
..
} => {
program.emit_insn(Insn::Copy {
src_reg: result_columns_start_reg + *column,
dst_reg: target_register,
amount: 0,
});
Ok(target_register)
}
}
}
ast::Expr::RowId { database: _, table } => {
let table_reference = referenced_tables.as_ref().unwrap().get(*table).unwrap();
let cursor_id = program.resolve_cursor_id(&table_reference.identifier);
program.emit_insn(Insn::RowId {
cursor_id,
dest: target_register,
});
Ok(target_register)
}
ast::Expr::InList { .. } => todo!(),
ast::Expr::InSelect { .. } => todo!(),
ast::Expr::InTable { .. } => todo!(),
ast::Expr::IsNull(_) => todo!(),
ast::Expr::Like { not, .. } => {
let like_reg = if *not {
program.alloc_register()
} else {
target_register
};
translate_like_base(program, referenced_tables, expr, like_reg, resolver)?;
if *not {
program.emit_insn(Insn::Not {
reg: like_reg,
dest: target_register,
});
}
Ok(target_register)
}
ast::Expr::Literal(lit) => match lit {
ast::Literal::Numeric(val) => {
if val.starts_with("0x") || val.starts_with("0X") {
// must be a hex decimal
let int_value = i64::from_str_radix(&val[2..], 16)?;
program.emit_insn(Insn::Integer {
value: int_value,
dest: target_register,
});
} else if let Ok(int_value) = val.parse::<i64>() {
program.emit_insn(Insn::Integer {
value: int_value,
dest: target_register,
});
} else {
// must be a float
program.emit_insn(Insn::Real {
value: val.parse()?,
dest: target_register,
});
}
Ok(target_register)
}
ast::Literal::String(s) => {
program.emit_insn(Insn::String8 {
value: sanitize_string(s),
dest: target_register,
});
Ok(target_register)
}
ast::Literal::Blob(s) => {
let bytes = s
.as_bytes()
.chunks_exact(2)
.map(|pair| {
// We assume that sqlite3-parser has already validated that
// the input is valid hex string, thus unwrap is safe.
let hex_byte = std::str::from_utf8(pair).unwrap();
u8::from_str_radix(hex_byte, 16).unwrap()
})
.collect();
program.emit_insn(Insn::Blob {
value: bytes,
dest: target_register,
});
Ok(target_register)
}
ast::Literal::Keyword(_) => todo!(),
ast::Literal::Null => {
program.emit_insn(Insn::Null {
dest: target_register,
dest_end: None,
});
Ok(target_register)
}
ast::Literal::CurrentDate => todo!(),
ast::Literal::CurrentTime => todo!(),
ast::Literal::CurrentTimestamp => todo!(),
},
ast::Expr::Name(_) => todo!(),
ast::Expr::NotNull(_) => todo!(),
ast::Expr::Parenthesized(exprs) => {
if exprs.is_empty() {
crate::bail_parse_error!("parenthesized expression with no arguments");
}
if exprs.len() == 1 {
translate_expr(
program,
referenced_tables,
&exprs[0],
target_register,
resolver,
)?;
} else {
// Parenthesized expressions with multiple arguments are reserved for special cases
// like `(a, b) IN ((1, 2), (3, 4))`.
todo!("TODO: parenthesized expression with multiple arguments not yet supported");
}
Ok(target_register)
}
ast::Expr::Qualified(_, _) => {
unreachable!("Qualified should be resolved to a Column before translation")
}
ast::Expr::Raise(_, _) => todo!(),
ast::Expr::Subquery(_) => todo!(),
ast::Expr::Unary(op, expr) => match (op, expr.as_ref()) {
(UnaryOperator::Positive, expr) => {
translate_expr(program, referenced_tables, expr, target_register, resolver)
}
(UnaryOperator::Negative, ast::Expr::Literal(ast::Literal::Numeric(numeric_value))) => {
// Special case: if we're negating "9223372036854775808", this is exactly MIN_INT64
// If we don't do this -1 * 9223372036854775808 will overflow and parse will fail
// and trigger conversion to Real.
if numeric_value == "9223372036854775808"
|| numeric_value.eq_ignore_ascii_case("0x7fffffffffffffff")
{
program.emit_insn(Insn::Integer {
value: i64::MIN,
dest: target_register,
});
} else {
if numeric_value.starts_with("0x") || numeric_value.starts_with("0X") {
// must be a hex decimal
let int_value = i64::from_str_radix(&numeric_value[2..], 16)?;
program.emit_insn(Insn::Integer {
value: -int_value,
dest: target_register,
});
} else if let Ok(value) = numeric_value.parse::<i64>() {
program.emit_insn(Insn::Integer {
value: value * -1,
dest: target_register,
});
} else {
let value = numeric_value.parse::<f64>()?;
program.emit_insn(Insn::Real {
value: value * -1 as f64,
dest: target_register,
});
}
}
Ok(target_register)
}
(UnaryOperator::Negative, _) => {
let value = 0;
let reg = program.alloc_register();
translate_expr(program, referenced_tables, expr, reg, resolver)?;
let zero_reg = program.alloc_register();
program.emit_insn(Insn::Integer {
value,
dest: zero_reg,
});
program.mark_last_insn_constant();
program.emit_insn(Insn::Subtract {
lhs: zero_reg,
rhs: reg,
dest: target_register,
});
Ok(target_register)
}
(UnaryOperator::BitwiseNot, ast::Expr::Literal(ast::Literal::Numeric(num_val))) => {
if num_val.starts_with("0x") || num_val.starts_with("0X") {
let int_value = i64::from_str_radix(&num_val[2..], 16)?;
program.emit_insn(Insn::Integer {
value: !int_value,
dest: target_register,
});
} else if let Ok(val) = num_val.parse::<i64>() {
program.emit_insn(Insn::Integer {
value: !val,
dest: target_register,
});
} else {
let num_val = num_val.parse::<f64>()? as i64;
program.emit_insn(Insn::Integer {
value: !num_val,
dest: target_register,
});
}
Ok(target_register)
}
(UnaryOperator::BitwiseNot, ast::Expr::Literal(ast::Literal::Null)) => {
program.emit_insn(Insn::Null {
dest: target_register,
dest_end: None,
});
Ok(target_register)
}
(UnaryOperator::BitwiseNot, _) => {
let reg = program.alloc_register();
translate_expr(program, referenced_tables, expr, reg, resolver)?;
program.emit_insn(Insn::BitNot {
reg,
dest: target_register,
});
Ok(target_register)
}
(UnaryOperator::Not, _) => {
let reg = program.alloc_register();
translate_expr(program, referenced_tables, expr, reg, resolver)?;
program.emit_insn(Insn::Not {
reg,
dest: target_register,
});
Ok(target_register)
}
},
ast::Expr::Variable(name) => {
let index = program.parameters.push(name);
program.emit_insn(Insn::Variable {
index,
dest: target_register,
});
Ok(target_register)
}
}
}
fn emit_binary_insn(
program: &mut ProgramBuilder,
op: &ast::Operator,
lhs: usize,
rhs: usize,
target_register: usize,
) -> Result<()> {
match op {
ast::Operator::NotEquals => {
let if_true_label = program.allocate_label();
wrap_eval_jump_expr_zero_or_null(
program,
Insn::Ne {
lhs,
rhs,
target_pc: if_true_label,
flags: CmpInsFlags::default(),
},
target_register,
if_true_label,
lhs,
rhs,
);
}
ast::Operator::Equals => {
let if_true_label = program.allocate_label();
wrap_eval_jump_expr_zero_or_null(
program,
Insn::Eq {
lhs,
rhs,
target_pc: if_true_label,
flags: CmpInsFlags::default(),
},
target_register,
if_true_label,
lhs,
rhs,
);
}
ast::Operator::Less => {
let if_true_label = program.allocate_label();
wrap_eval_jump_expr_zero_or_null(
program,
Insn::Lt {
lhs,
rhs,
target_pc: if_true_label,
flags: CmpInsFlags::default(),
},
target_register,
if_true_label,
lhs,
rhs,
);
}
ast::Operator::LessEquals => {
let if_true_label = program.allocate_label();
wrap_eval_jump_expr_zero_or_null(
program,
Insn::Le {
lhs,
rhs,
target_pc: if_true_label,
flags: CmpInsFlags::default(),
},
target_register,
if_true_label,
lhs,
rhs,
);
}
ast::Operator::Greater => {
let if_true_label = program.allocate_label();
wrap_eval_jump_expr_zero_or_null(
program,
Insn::Gt {
lhs,
rhs,
target_pc: if_true_label,
flags: CmpInsFlags::default(),
},
target_register,
if_true_label,
lhs,
rhs,
);
}
ast::Operator::GreaterEquals => {
let if_true_label = program.allocate_label();
wrap_eval_jump_expr_zero_or_null(
program,
Insn::Ge {
lhs,
rhs,
target_pc: if_true_label,
flags: CmpInsFlags::default(),
},
target_register,
if_true_label,
lhs,
rhs,
);
}
ast::Operator::Add => {
program.emit_insn(Insn::Add {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::Subtract => {
program.emit_insn(Insn::Subtract {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::Multiply => {
program.emit_insn(Insn::Multiply {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::Divide => {
program.emit_insn(Insn::Divide {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::Modulus => {
program.emit_insn(Insn::Remainder {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::And => {
program.emit_insn(Insn::And {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::Or => {
program.emit_insn(Insn::Or {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::BitwiseAnd => {
program.emit_insn(Insn::BitAnd {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::BitwiseOr => {
program.emit_insn(Insn::BitOr {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::RightShift => {
program.emit_insn(Insn::ShiftRight {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::LeftShift => {
program.emit_insn(Insn::ShiftLeft {
lhs,
rhs,
dest: target_register,
});
}
ast::Operator::Is => {
let if_true_label = program.allocate_label();
wrap_eval_jump_expr(
program,
Insn::Eq {
lhs,
rhs,
target_pc: if_true_label,
flags: CmpInsFlags::default().null_eq(),
},
target_register,
if_true_label,
);
}
ast::Operator::IsNot => {
let if_true_label = program.allocate_label();
wrap_eval_jump_expr(
program,
Insn::Ne {
lhs,
rhs,
target_pc: if_true_label,
flags: CmpInsFlags::default().null_eq(),
},
target_register,
if_true_label,
);
}
#[cfg(feature = "json")]
op @ (ast::Operator::ArrowRight | ast::Operator::ArrowRightShift) => {
let json_func = match op {
ast::Operator::ArrowRight => JsonFunc::JsonArrowExtract,
ast::Operator::ArrowRightShift => JsonFunc::JsonArrowShiftExtract,
_ => unreachable!(),
};
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: lhs,
dest: target_register,
func: FuncCtx {
func: Func::Json(json_func),
arg_count: 2,
},
})
}
ast::Operator::Concat => {
program.emit_insn(Insn::Concat {
lhs,
rhs,
dest: target_register,
});
}
other_unimplemented => todo!("{:?}", other_unimplemented),
}
Ok(())
}
/// The base logic for translating LIKE and GLOB expressions.
/// The logic for handling "NOT LIKE" is different depending on whether the expression
/// is a conditional jump or not. This is why the caller handles the "NOT LIKE" behavior;
/// see [translate_condition_expr] and [translate_expr] for implementations.
fn translate_like_base(
program: &mut ProgramBuilder,
referenced_tables: Option<&[TableReference]>,
expr: &ast::Expr,
target_register: usize,
resolver: &Resolver,
) -> Result<usize> {
let ast::Expr::Like {
lhs,
op,
rhs,
escape,
..
} = expr
else {
crate::bail_parse_error!("expected Like expression");
};
match op {
ast::LikeOperator::Like | ast::LikeOperator::Glob => {
let arg_count = if matches!(escape, Some(_)) { 3 } else { 2 };
let start_reg = program.alloc_registers(arg_count);
let mut constant_mask = 0;
translate_and_mark(program, referenced_tables, lhs, start_reg + 1, resolver)?;
let _ = translate_expr(program, referenced_tables, rhs, start_reg, resolver)?;
if arg_count == 3 {
if let Some(escape) = escape {
translate_and_mark(
program,
referenced_tables,
escape,
start_reg + 2,
resolver,
)?;
}
}
if matches!(rhs.as_ref(), ast::Expr::Literal(_)) {
program.mark_last_insn_constant();
constant_mask = 1;
}
let func = match op {
ast::LikeOperator::Like => ScalarFunc::Like,
ast::LikeOperator::Glob => ScalarFunc::Glob,
_ => unreachable!(),
};
program.emit_insn(Insn::Function {
constant_mask,
start_reg,
dest: target_register,
func: FuncCtx {
func: Func::Scalar(func),
arg_count,
},
});
}
ast::LikeOperator::Match => todo!(),
ast::LikeOperator::Regexp => todo!(),
}
Ok(target_register)
}
/// Emits a whole insn for a function call.
/// Assumes the number of parameters is valid for the given function.
/// Returns the target register for the function.
fn translate_function(
program: &mut ProgramBuilder,
args: &[ast::Expr],
referenced_tables: Option<&[TableReference]>,
resolver: &Resolver,
target_register: usize,
func_ctx: FuncCtx,
) -> Result<usize> {
let start_reg = program.alloc_registers(args.len());
let mut current_reg = start_reg;
for arg in args.iter() {
translate_expr(program, referenced_tables, arg, current_reg, resolver)?;
current_reg += 1;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
fn wrap_eval_jump_expr(
program: &mut ProgramBuilder,
insn: Insn,
target_register: usize,
if_true_label: BranchOffset,
) {
program.emit_insn(Insn::Integer {
value: 1, // emit True by default
dest: target_register,
});
program.emit_insn(insn);
program.emit_insn(Insn::Integer {
value: 0, // emit False if we reach this point (no jump)
dest: target_register,
});
program.preassign_label_to_next_insn(if_true_label);
}
fn wrap_eval_jump_expr_zero_or_null(
program: &mut ProgramBuilder,
insn: Insn,
target_register: usize,
if_true_label: BranchOffset,
e1_reg: usize,
e2_reg: usize,
) {
program.emit_insn(Insn::Integer {
value: 1, // emit True by default
dest: target_register,
});
program.emit_insn(insn);
program.emit_insn(Insn::ZeroOrNull {
rg1: e1_reg,
rg2: e2_reg,
dest: target_register,
});
program.preassign_label_to_next_insn(if_true_label);
}
pub fn maybe_apply_affinity(col_type: Type, target_register: usize, program: &mut ProgramBuilder) {
if col_type == Type::Real {
program.emit_insn(Insn::RealAffinity {
register: target_register,
})
}
}
pub fn translate_and_mark(
program: &mut ProgramBuilder,
referenced_tables: Option<&[TableReference]>,
expr: &ast::Expr,
target_register: usize,
resolver: &Resolver,
) -> Result<()> {
translate_expr(program, referenced_tables, expr, target_register, resolver)?;
if matches!(expr, ast::Expr::Literal(_)) {
program.mark_last_insn_constant();
}
Ok(())
}
/// Sanitaizes a string literal by removing single quote at front and back
/// and escaping double single quotes
pub fn sanitize_string(input: &str) -> String {
input[1..input.len() - 1].replace("''", "'").to_string()
}