Files
turso/core/translate/expr.rs
2024-09-16 18:38:42 +09:00

1853 lines
76 KiB
Rust

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<BTreeTable>, String)],
expr: &ast::Expr,
cursor_hint: Option<usize>,
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::<i64>();
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<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_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<BTreeTable>, String)]>,
expr: &ast::Expr,
cursor_hint: Option<usize>,
cached_results: Option<&Vec<&CachedResult>>,
) -> Result<usize> {
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<BTreeTable>, String)]>,
expr: &ast::Expr,
target_register: usize,
cursor_hint: Option<usize>,
cached_results: Option<&Vec<&CachedResult>>,
) -> Result<usize> {
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> =
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::<i64>();
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::<i64>();
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::<f64>()?,
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<BTreeTable>, String)],
cursor_hint: Option<usize>,
) -> 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<BTreeTable>, String)]>,
cursor_hint: Option<usize>,
) -> 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<BTreeTable>, String)],
agg: &Aggregate,
target_register: usize,
cursor_hint: Option<usize>,
) -> Result<usize> {
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)
}