use crate::{function::JsonFunc, Result}; use sqlite3_parser::ast::{self, UnaryOperator}; use std::rc::Rc; use super::optimizer::CachedResult; use crate::function::{AggFunc, Func, FuncCtx, ScalarFunc}; use crate::schema::{Table, Type}; use crate::util::normalize_ident; use crate::{ schema::BTreeTable, vdbe::{builder::ProgramBuilder, BranchOffset, Insn}, }; use super::plan::Aggregate; #[derive(Default, 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, } pub fn translate_condition_expr( program: &mut ProgramBuilder, referenced_tables: &[(Rc, String)], expr: &ast::Expr, cursor_hint: Option, condition_metadata: ConditionMetadata, ) -> Result<()> { match expr { ast::Expr::Between { .. } => todo!(), ast::Expr::Binary(lhs, ast::Operator::And, rhs) => { // In a binary AND, never jump to the 'jump_target_when_true' label on the first condition, because // the second condition must also be true. let _ = translate_condition_expr( program, referenced_tables, lhs, cursor_hint, ConditionMetadata { jump_if_condition_is_true: false, ..condition_metadata }, ); let _ = translate_condition_expr( program, referenced_tables, rhs, cursor_hint, condition_metadata, ); } ast::Expr::Binary(lhs, ast::Operator::Or, rhs) => { let jump_target_when_false = program.allocate_label(); let _ = translate_condition_expr( program, referenced_tables, lhs, cursor_hint, ConditionMetadata { // If the first condition is true, we don't need to evaluate the second condition. jump_if_condition_is_true: true, jump_target_when_false, ..condition_metadata }, ); program.resolve_label(jump_target_when_false, program.offset()); let _ = translate_condition_expr( program, referenced_tables, rhs, cursor_hint, condition_metadata, ); } ast::Expr::Binary(lhs, op, rhs) => { let lhs_reg = program.alloc_register(); let _ = translate_expr( program, Some(referenced_tables), lhs, lhs_reg, cursor_hint, None, ); if let ast::Expr::Literal(_) = lhs.as_ref() { program.mark_last_insn_constant() } let rhs_reg = program.alloc_register(); let _ = translate_expr( program, Some(referenced_tables), rhs, rhs_reg, cursor_hint, None, ); if let ast::Expr::Literal(_) = rhs.as_ref() { program.mark_last_insn_constant() } match op { ast::Operator::Greater => { if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::Gt { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_true, }, condition_metadata.jump_target_when_true, ) } else { program.emit_insn_with_label_dependency( Insn::Le { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_false, }, condition_metadata.jump_target_when_false, ) } } ast::Operator::GreaterEquals => { if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::Ge { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_true, }, condition_metadata.jump_target_when_true, ) } else { program.emit_insn_with_label_dependency( Insn::Lt { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_false, }, condition_metadata.jump_target_when_false, ) } } ast::Operator::Less => { if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::Lt { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_true, }, condition_metadata.jump_target_when_true, ) } else { program.emit_insn_with_label_dependency( Insn::Ge { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_false, }, condition_metadata.jump_target_when_false, ) } } ast::Operator::LessEquals => { if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::Le { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_true, }, condition_metadata.jump_target_when_true, ) } else { program.emit_insn_with_label_dependency( Insn::Gt { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_false, }, condition_metadata.jump_target_when_false, ) } } ast::Operator::Equals => { if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::Eq { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_true, }, condition_metadata.jump_target_when_true, ) } else { program.emit_insn_with_label_dependency( Insn::Ne { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_false, }, condition_metadata.jump_target_when_false, ) } } ast::Operator::NotEquals => { if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::Ne { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_true, }, condition_metadata.jump_target_when_true, ) } else { program.emit_insn_with_label_dependency( Insn::Eq { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_false, }, condition_metadata.jump_target_when_false, ) } } ast::Operator::Is => todo!(), ast::Operator::IsNot => todo!(), _ => { todo!("op {:?} not implemented", op); } } } ast::Expr::Literal(lit) => match lit { ast::Literal::Numeric(val) => { let maybe_int = val.parse::(); if let Ok(int_value) = maybe_int { let reg = program.alloc_register(); program.emit_insn(Insn::Integer { value: int_value, dest: reg, }); if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::If { reg, target_pc: condition_metadata.jump_target_when_true, null_reg: reg, }, condition_metadata.jump_target_when_true, ) } else { program.emit_insn_with_label_dependency( Insn::IfNot { reg, target_pc: condition_metadata.jump_target_when_false, null_reg: reg, }, condition_metadata.jump_target_when_false, ) } } else { crate::bail_parse_error!("unsupported literal type in condition"); } } ast::Literal::String(string) => { let reg = program.alloc_register(); program.emit_insn(Insn::String8 { value: string.clone(), dest: reg, }); if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::If { reg, target_pc: condition_metadata.jump_target_when_true, null_reg: reg, }, condition_metadata.jump_target_when_true, ) } else { program.emit_insn_with_label_dependency( Insn::IfNot { reg, target_pc: condition_metadata.jump_target_when_false, null_reg: reg, }, condition_metadata.jump_target_when_false, ) } } unimpl => todo!("literal {:?} not implemented", unimpl), }, ast::Expr::InList { lhs, not, rhs } => { // lhs is e.g. a column reference // rhs is an Option> // 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_with_label_dependency( Insn::Goto { target_pc: condition_metadata.jump_target_when_true, }, condition_metadata.jump_target_when_true, ); } } else { program.emit_insn_with_label_dependency( Insn::Goto { target_pc: condition_metadata.jump_target_when_false, }, 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, cursor_hint, None, )?; 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, cursor_hint, None, )?; // 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_with_label_dependency( Insn::Eq { lhs: lhs_reg, rhs: rhs_reg, target_pc: jump_target_when_true, }, jump_target_when_true, ); } 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_with_label_dependency( Insn::Ne { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_false, }, condition_metadata.jump_target_when_false, ); } } // 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_with_label_dependency( Insn::Goto { target_pc: condition_metadata.jump_target_when_true, }, 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, cursor_hint, None, )?; program.emit_insn_with_label_dependency( Insn::Eq { lhs: lhs_reg, rhs: rhs_reg, target_pc: condition_metadata.jump_target_when_false, }, condition_metadata.jump_target_when_false, ); } // 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_with_label_dependency( Insn::Goto { target_pc: condition_metadata.jump_target_when_true, }, 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 { lhs, not, op, rhs, escape: _, } => { let cur_reg = program.alloc_register(); assert!(match rhs.as_ref() { ast::Expr::Literal(_) => true, _ => false, }); match op { ast::LikeOperator::Like => { let pattern_reg = program.alloc_register(); let column_reg = program.alloc_register(); // LIKE(pattern, column). We should translate the pattern first before the column let _ = translate_expr( program, Some(referenced_tables), rhs, pattern_reg, cursor_hint, None, )?; program.mark_last_insn_constant(); let _ = translate_expr( program, Some(referenced_tables), lhs, column_reg, cursor_hint, None, )?; program.emit_insn(Insn::Function { // Only constant patterns for LIKE are supported currently, so this // is always 1 constant_mask: 1, start_reg: pattern_reg, dest: cur_reg, func: FuncCtx { func: Func::Scalar(ScalarFunc::Like), arg_count: 2, }, }); } ast::LikeOperator::Glob => todo!(), ast::LikeOperator::Match => todo!(), ast::LikeOperator::Regexp => todo!(), } if !*not { if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::If { reg: cur_reg, target_pc: condition_metadata.jump_target_when_true, null_reg: cur_reg, }, condition_metadata.jump_target_when_true, ); } else { program.emit_insn_with_label_dependency( Insn::IfNot { reg: cur_reg, target_pc: condition_metadata.jump_target_when_false, null_reg: cur_reg, }, condition_metadata.jump_target_when_false, ); } } else if condition_metadata.jump_if_condition_is_true { program.emit_insn_with_label_dependency( Insn::IfNot { reg: cur_reg, target_pc: condition_metadata.jump_target_when_true, null_reg: cur_reg, }, condition_metadata.jump_target_when_true, ); } else { program.emit_insn_with_label_dependency( Insn::If { reg: cur_reg, target_pc: condition_metadata.jump_target_when_false, null_reg: cur_reg, }, condition_metadata.jump_target_when_false, ); } } _ => todo!("op {:?} not implemented", expr), } Ok(()) } pub fn get_cached_or_translate( program: &mut ProgramBuilder, referenced_tables: Option<&[(Rc, String)]>, expr: &ast::Expr, cursor_hint: Option, cached_results: Option<&Vec<&CachedResult>>, ) -> Result { if let Some(cached_results) = cached_results { if let Some(cached_result) = cached_results .iter() .find(|cached_result| cached_result.source_expr == *expr) { return Ok(cached_result.register_idx); } } let reg = program.alloc_register(); translate_expr( program, referenced_tables, expr, reg, cursor_hint, cached_results, )?; Ok(reg) } pub fn translate_expr( program: &mut ProgramBuilder, referenced_tables: Option<&[(Rc, String)]>, expr: &ast::Expr, target_register: usize, cursor_hint: Option, cached_results: Option<&Vec<&CachedResult>>, ) -> Result { if let Some(cached_results) = &cached_results { if let Some(cached_result) = cached_results .iter() .find(|cached_result| cached_result.source_expr == *expr) { program.emit_insn(Insn::Copy { src_reg: cached_result.register_idx, dst_reg: target_register, amount: 0, }); return Ok(target_register); } } match expr { ast::Expr::Between { .. } => todo!(), ast::Expr::Binary(e1, op, e2) => { let e1_reg = get_cached_or_translate( program, referenced_tables, e1, cursor_hint, cached_results, )?; let e2_reg = get_cached_or_translate( program, referenced_tables, e2, cursor_hint, cached_results, )?; match op { ast::Operator::NotEquals => { let if_true_label = program.allocate_label(); wrap_eval_jump_expr( program, Insn::Ne { lhs: e1_reg, rhs: e2_reg, target_pc: if_true_label, }, target_register, if_true_label, ); } ast::Operator::Equals => { let if_true_label = program.allocate_label(); wrap_eval_jump_expr( program, Insn::Eq { lhs: e1_reg, rhs: e2_reg, target_pc: if_true_label, }, target_register, if_true_label, ); } ast::Operator::Less => { let if_true_label = program.allocate_label(); wrap_eval_jump_expr( program, Insn::Lt { lhs: e1_reg, rhs: e2_reg, target_pc: if_true_label, }, target_register, if_true_label, ); } ast::Operator::LessEquals => { let if_true_label = program.allocate_label(); wrap_eval_jump_expr( program, Insn::Le { lhs: e1_reg, rhs: e2_reg, target_pc: if_true_label, }, target_register, if_true_label, ); } ast::Operator::Greater => { let if_true_label = program.allocate_label(); wrap_eval_jump_expr( program, Insn::Gt { lhs: e1_reg, rhs: e2_reg, target_pc: if_true_label, }, target_register, if_true_label, ); } ast::Operator::GreaterEquals => { let if_true_label = program.allocate_label(); wrap_eval_jump_expr( program, Insn::Ge { lhs: e1_reg, rhs: e2_reg, target_pc: if_true_label, }, target_register, if_true_label, ); } ast::Operator::Add => { program.emit_insn(Insn::Add { lhs: e1_reg, rhs: e2_reg, dest: target_register, }); } ast::Operator::Multiply => { program.emit_insn(Insn::Multiply { lhs: e1_reg, rhs: e2_reg, dest: target_register, }); } other_unimplemented => todo!("{:?}", other_unimplemented), } Ok(target_register) } ast::Expr::Case { .. } => todo!(), ast::Expr::Cast { .. } => todo!(), 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_type: Option = Func::resolve_function(normalize_ident(name.0.as_str()).as_str(), args_count).ok(); 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::Json(j) => match j { JsonFunc::JSON => { 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 regs = program.alloc_register(); translate_expr( program, referenced_tables, &args[0], regs, cursor_hint, cached_results, )?; 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::Char => { let args = args.clone().unwrap_or_else(Vec::new); for arg in args.iter() { let reg = program.alloc_register(); translate_expr( program, referenced_tables, arg, reg, cursor_hint, cached_results, )?; } program.emit_insn(Insn::Function { constant_mask: 0, start_reg: target_register + 1, dest: target_register, func: func_ctx, }); Ok(target_register) } ScalarFunc::Coalesce => { 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() ); }; // 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, cursor_hint, cached_results, )?; if index < args.len() - 1 { program.emit_insn_with_label_dependency( Insn::NotNull { reg, target_pc: label_coalesce_end, }, label_coalesce_end, ); } } program.preassign_label_to_next_insn(label_coalesce_end); 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() ); }; for arg in args.iter() { let reg = program.alloc_register(); translate_expr( program, referenced_tables, arg, reg, cursor_hint, cached_results, )?; } program.emit_insn(Insn::Function { constant_mask: 0, start_reg: target_register + 1, dest: target_register, func: func_ctx, }); Ok(target_register) } ScalarFunc::ConcatWs => { let args = match args { Some(args) if args.len() >= 2 => args, Some(_) => crate::bail_parse_error!( "{} function requires at least 2 arguments", srf.to_string() ), None => crate::bail_parse_error!( "{} function requires arguments", srf.to_string() ), }; let temp_register = program.alloc_register(); for arg in args.iter() { let reg = program.alloc_register(); translate_expr( program, referenced_tables, arg, reg, cursor_hint, cached_results, )?; } 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, cursor_hint, cached_results, )?; program.emit_insn(Insn::NotNull { reg: temp_reg, target_pc: program.offset() + 2, }); translate_expr( program, referenced_tables, &args[1], temp_reg, cursor_hint, cached_results, )?; program.emit_insn(Insn::Copy { src_reg: temp_reg, dst_reg: target_register, amount: 0, }); Ok(target_register) } 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() ); }; for arg in args { let reg = program.alloc_register(); let _ = translate_expr( program, referenced_tables, arg, reg, cursor_hint, cached_results, )?; if let ast::Expr::Literal(_) = arg { program.mark_last_insn_constant() } } program.emit_insn(Insn::Function { // Only constant patterns for LIKE are supported currently, so this // is always 1 constant_mask: 1, start_reg: target_register + 1, dest: target_register, func: func_ctx, }); Ok(target_register) } ScalarFunc::Abs | ScalarFunc::Lower | ScalarFunc::Upper | ScalarFunc::Length | ScalarFunc::Unicode | ScalarFunc::Quote | ScalarFunc::Sign => { let args = if let Some(args) = args { if args.len() != 1 { crate::bail_parse_error!( "{} function with not exactly 1 argument", srf.to_string() ); } args } else { crate::bail_parse_error!( "{} function with no arguments", srf.to_string() ); }; let regs = program.alloc_register(); translate_expr( program, referenced_tables, &args[0], regs, cursor_hint, cached_results, )?; program.emit_insn(Insn::Function { constant_mask: 0, start_reg: regs, 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 => { if let Some(args) = args { for arg in args.iter() { // register containing result of each argument expression let target_reg = program.alloc_register(); _ = translate_expr( program, referenced_tables, arg, target_reg, cursor_hint, cached_results, )?; } } program.emit_insn(Insn::Function { constant_mask: 0, start_reg: target_register + 1, 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(); translate_expr( program, referenced_tables, &args[0], str_reg, cursor_hint, cached_results, )?; translate_expr( program, referenced_tables, &args[1], start_reg, cursor_hint, cached_results, )?; if args.len() == 3 { translate_expr( program, referenced_tables, &args[2], length_reg, cursor_hint, cached_results, )?; } program.emit_insn(Insn::Function { constant_mask: 0, start_reg: str_reg, dest: target_register, func: func_ctx, }); Ok(target_register) } ScalarFunc::UnixEpoch => { let mut start_reg = 0; if let Some(args) = args { if args.len() > 1 { crate::bail_parse_error!("epoch function with > 1 arguments. Modifiers are not yet supported."); } else if args.len() == 1 { let arg_reg = program.alloc_register(); let _ = translate_expr( program, referenced_tables, &args[0], arg_reg, cursor_hint, cached_results, )?; 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 => { if let Some(args) = args { for arg in args.iter() { // register containing result of each argument expression let target_reg = program.alloc_register(); _ = translate_expr( program, referenced_tables, arg, target_reg, cursor_hint, cached_results, )?; } } program.emit_insn(Insn::Function { constant_mask: 0, start_reg: target_register + 1, dest: target_register, func: func_ctx, }); Ok(target_register) } ScalarFunc::Trim | ScalarFunc::LTrim | ScalarFunc::RTrim | ScalarFunc::Round => { let args = if let Some(args) = args { if args.len() > 2 { crate::bail_parse_error!( "{} function with more than 2 arguments", srf.to_string() ); } args } else { crate::bail_parse_error!( "{} function with no arguments", srf.to_string() ); }; for arg in args.iter() { let reg = program.alloc_register(); translate_expr( program, referenced_tables, arg, reg, cursor_hint, cached_results, )?; if let ast::Expr::Literal(_) = arg { program.mark_last_insn_constant(); } } program.emit_insn(Insn::Function { constant_mask: 0, start_reg: target_register + 1, 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"); }; for arg in args { let reg = program.alloc_register(); let _ = translate_expr( program, referenced_tables, arg, reg, cursor_hint, cached_results, )?; if let ast::Expr::Literal(_) = arg { program.mark_last_insn_constant() } } program.emit_insn(Insn::Function { constant_mask: 0, start_reg: target_register + 1, 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"); }; for arg in args { let reg = program.alloc_register(); let _ = translate_expr( program, referenced_tables, arg, reg, cursor_hint, cached_results, )?; if let ast::Expr::Literal(_) = arg { program.mark_last_insn_constant() } } program.emit_insn(Insn::Function { constant_mask: 0, start_reg: target_register + 1, dest: target_register, func: func_ctx, }); Ok(target_register) } ScalarFunc::Nullif => { let args = if let Some(args) = args { if args.len() != 2 { crate::bail_parse_error!( "nullif function must have two argument" ); } args } else { crate::bail_parse_error!("nullif function with no arguments"); }; let first_reg = program.alloc_register(); translate_expr( program, referenced_tables, &args[0], first_reg, cursor_hint, cached_results, )?; let second_reg = program.alloc_register(); translate_expr( program, referenced_tables, &args[1], second_reg, cursor_hint, cached_results, )?; 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: 1, }); Ok(target_register) } } } } } ast::Expr::FunctionCallStar { .. } => todo!(), ast::Expr::Id(ident) => { // let (idx, col) = table.unwrap().get_column(&ident.0).unwrap(); let (idx, col_type, cursor_id, is_rowid_alias) = resolve_ident_table(program, &ident.0, referenced_tables, cursor_hint)?; if is_rowid_alias { program.emit_insn(Insn::RowId { cursor_id, dest: target_register, }); } else { program.emit_insn(Insn::Column { column: idx, dest: target_register, cursor_id, }); } maybe_apply_affinity(col_type, target_register, program); Ok(target_register) } ast::Expr::InList { .. } => todo!(), ast::Expr::InSelect { .. } => todo!(), ast::Expr::InTable { .. } => todo!(), ast::Expr::IsNull(_) => todo!(), ast::Expr::Like { .. } => todo!(), ast::Expr::Literal(lit) => match lit { ast::Literal::Numeric(val) => { let maybe_int = val.parse::(); if let Ok(int_value) = maybe_int { program.emit_insn(Insn::Integer { value: int_value, dest: target_register, }); } else { // must be a float program.emit_insn(Insn::Real { value: val.parse().unwrap(), dest: target_register, }); } Ok(target_register) } ast::Literal::String(s) => { program.emit_insn(Insn::String8 { value: s[1..s.len() - 1].to_string(), dest: target_register, }); Ok(target_register) } ast::Literal::Blob(_) => todo!(), 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(_) => todo!(), ast::Expr::Qualified(tbl, ident) => { let (idx, col_type, cursor_id, is_primary_key) = resolve_ident_qualified( program, &tbl.0, &ident.0, referenced_tables.unwrap(), cursor_hint, )?; if is_primary_key { program.emit_insn(Insn::RowId { cursor_id, dest: target_register, }); } else { program.emit_insn(Insn::Column { column: idx, dest: target_register, cursor_id, }); } maybe_apply_affinity(col_type, target_register, program); Ok(target_register) } ast::Expr::Raise(_, _) => todo!(), ast::Expr::Subquery(_) => todo!(), ast::Expr::Unary(op, expr) => match (op, expr.as_ref()) { (UnaryOperator::Negative, ast::Expr::Literal(ast::Literal::Numeric(numeric_value))) => { let maybe_int = numeric_value.parse::(); if let Ok(value) = maybe_int { program.emit_insn(Insn::Integer { value: -value, dest: target_register, }); } else { program.emit_insn(Insn::Real { value: -numeric_value.parse::()?, dest: target_register, }); } Ok(target_register) } _ => todo!(), }, ast::Expr::Variable(_) => todo!(), } } 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_with_label_dependency(insn, if_true_label); 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); } pub fn resolve_ident_qualified( program: &ProgramBuilder, table_name: &String, ident: &String, referenced_tables: &[(Rc, String)], cursor_hint: Option, ) -> Result<(usize, Type, usize, bool)> { let ident = normalize_ident(ident); let table_name = normalize_ident(table_name); for (catalog_table, identifier) in referenced_tables.iter() { if *identifier == table_name { let res = catalog_table .columns .iter() .enumerate() .find(|(_, col)| col.name == *ident) .map(|(idx, col)| (idx, col.ty, col.primary_key)); let mut idx; let mut col_type; let mut is_primary_key; if res.is_some() { (idx, col_type, is_primary_key) = res.unwrap(); // overwrite if cursor hint is provided if let Some(cursor_hint) = cursor_hint { let cols = &program.cursor_ref[cursor_hint].1; if let Some(res) = cols.as_ref().and_then(|res| { res.columns() .iter() .enumerate() .find(|x| x.1.name == format!("{}.{}", table_name, ident)) }) { idx = res.0; col_type = res.1.ty; is_primary_key = res.1.primary_key; } } let cursor_id = program.resolve_cursor_id(identifier, cursor_hint); return Ok((idx, col_type, cursor_id, is_primary_key)); } } } crate::bail_parse_error!( "column with qualified name {}.{} not found", table_name, ident ); } pub fn resolve_ident_table( program: &ProgramBuilder, ident: &String, referenced_tables: Option<&[(Rc, String)]>, cursor_hint: Option, ) -> Result<(usize, Type, usize, bool)> { let ident = normalize_ident(ident); let mut found = Vec::new(); for (catalog_table, identifier) in referenced_tables.unwrap() { let res = catalog_table .columns .iter() .enumerate() .find(|(_, col)| col.name == *ident) .map(|(idx, col)| (idx, col.ty, catalog_table.column_is_rowid_alias(col))); let mut idx; let mut col_type; let mut is_rowid_alias; if res.is_some() { (idx, col_type, is_rowid_alias) = res.unwrap(); // overwrite if cursor hint is provided if let Some(cursor_hint) = cursor_hint { let cols = &program.cursor_ref[cursor_hint].1; if let Some(res) = cols.as_ref().and_then(|res| { res.columns() .iter() .enumerate() .find(|x| x.1.name == *ident) }) { idx = res.0; col_type = res.1.ty; is_rowid_alias = catalog_table.column_is_rowid_alias(res.1); } } let cursor_id = program.resolve_cursor_id(identifier, cursor_hint); found.push((idx, col_type, cursor_id, is_rowid_alias)); } } if found.len() == 1 { return Ok(found[0]); } if found.is_empty() { crate::bail_parse_error!("column with name {} not found", ident.as_str()); } crate::bail_parse_error!("ambiguous column name {}", ident.as_str()); } pub fn maybe_apply_affinity(col_type: Type, target_register: usize, program: &mut ProgramBuilder) { if col_type == crate::schema::Type::Real { program.emit_insn(Insn::RealAffinity { register: target_register, }) } } pub fn translate_table_columns( program: &mut ProgramBuilder, cursor_id: usize, table: &Table, start_column_offset: usize, start_reg: usize, ) -> usize { let mut cur_reg = start_reg; for i in start_column_offset..table.columns().len() { let is_rowid = table.column_is_rowid_alias(table.get_column_at(i)); let col_type = &table.get_column_at(i).ty; if is_rowid { program.emit_insn(Insn::RowId { cursor_id, dest: cur_reg, }); } else { program.emit_insn(Insn::Column { cursor_id, column: i, dest: cur_reg, }); } maybe_apply_affinity(*col_type, cur_reg, program); cur_reg += 1; } cur_reg } pub fn translate_aggregation( program: &mut ProgramBuilder, referenced_tables: &[(Rc, String)], agg: &Aggregate, target_register: usize, cursor_hint: Option, ) -> Result { let dest = match agg.func { AggFunc::Avg => { if agg.args.len() != 1 { crate::bail_parse_error!("avg bad number of arguments"); } let expr = &agg.args[0]; let expr_reg = program.alloc_register(); let _ = translate_expr( program, Some(referenced_tables), expr, expr_reg, cursor_hint, None, )?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, delimiter: 0, func: AggFunc::Avg, }); target_register } AggFunc::Count => { let expr_reg = if agg.args.is_empty() { program.alloc_register() } else { let expr = &agg.args[0]; let expr_reg = program.alloc_register(); let _ = translate_expr( program, Some(referenced_tables), expr, expr_reg, cursor_hint, None, ); expr_reg }; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, delimiter: 0, func: AggFunc::Count, }); target_register } AggFunc::GroupConcat => { if agg.args.len() != 1 && agg.args.len() != 2 { crate::bail_parse_error!("group_concat bad number of arguments"); } let expr_reg = program.alloc_register(); let delimiter_reg = program.alloc_register(); let expr = &agg.args[0]; let delimiter_expr: ast::Expr; if agg.args.len() == 2 { match &agg.args[1] { ast::Expr::Id(ident) => { if ident.0.starts_with('"') { delimiter_expr = ast::Expr::Literal(ast::Literal::String(ident.0.to_string())); } else { delimiter_expr = agg.args[1].clone(); } } ast::Expr::Literal(ast::Literal::String(s)) => { delimiter_expr = ast::Expr::Literal(ast::Literal::String(s.to_string())); } _ => crate::bail_parse_error!("Incorrect delimiter parameter"), }; } else { delimiter_expr = ast::Expr::Literal(ast::Literal::String(String::from("\",\""))); } translate_expr( program, Some(referenced_tables), expr, expr_reg, cursor_hint, None, )?; translate_expr( program, Some(referenced_tables), &delimiter_expr, delimiter_reg, cursor_hint, None, )?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, delimiter: delimiter_reg, func: AggFunc::GroupConcat, }); target_register } AggFunc::Max => { if agg.args.len() != 1 { crate::bail_parse_error!("max bad number of arguments"); } let expr = &agg.args[0]; let expr_reg = program.alloc_register(); let _ = translate_expr( program, Some(referenced_tables), expr, expr_reg, cursor_hint, None, )?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, delimiter: 0, func: AggFunc::Max, }); target_register } AggFunc::Min => { if agg.args.len() != 1 { crate::bail_parse_error!("min bad number of arguments"); } let expr = &agg.args[0]; let expr_reg = program.alloc_register(); let _ = translate_expr( program, Some(referenced_tables), expr, expr_reg, cursor_hint, None, )?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, delimiter: 0, func: AggFunc::Min, }); target_register } AggFunc::StringAgg => { if agg.args.len() != 2 { crate::bail_parse_error!("string_agg bad number of arguments"); } let expr_reg = program.alloc_register(); let delimiter_reg = program.alloc_register(); let expr = &agg.args[0]; let delimiter_expr: ast::Expr; match &agg.args[1] { ast::Expr::Id(ident) => { if ident.0.starts_with('"') { crate::bail_parse_error!("no such column: \",\" - should this be a string literal in single-quotes?"); } else { delimiter_expr = agg.args[1].clone(); } } ast::Expr::Literal(ast::Literal::String(s)) => { delimiter_expr = ast::Expr::Literal(ast::Literal::String(s.to_string())); } _ => crate::bail_parse_error!("Incorrect delimiter parameter"), }; translate_expr( program, Some(referenced_tables), expr, expr_reg, cursor_hint, None, )?; translate_expr( program, Some(referenced_tables), &delimiter_expr, delimiter_reg, cursor_hint, None, )?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, delimiter: delimiter_reg, func: AggFunc::StringAgg, }); target_register } AggFunc::Sum => { if agg.args.len() != 1 { crate::bail_parse_error!("sum bad number of arguments"); } let expr = &agg.args[0]; let expr_reg = program.alloc_register(); let _ = translate_expr( program, Some(referenced_tables), expr, expr_reg, cursor_hint, None, )?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, delimiter: 0, func: AggFunc::Sum, }); target_register } AggFunc::Total => { if agg.args.len() != 1 { crate::bail_parse_error!("total bad number of arguments"); } let expr = &agg.args[0]; let expr_reg = program.alloc_register(); let _ = translate_expr( program, Some(referenced_tables), expr, expr_reg, cursor_hint, None, )?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, delimiter: 0, func: AggFunc::Total, }); target_register } }; Ok(dest) }