Files
turso/core/translate/select.rs
2024-07-24 21:20:15 +03:00

909 lines
32 KiB
Rust

use std::rc::Rc;
use anyhow::Result;
use sqlite3_parser::ast::{self, JoinOperator, JoinType};
use crate::function::AggFunc;
use crate::schema::{Column, PseudoTable, Schema};
use crate::translate::expr::analyze_columns;
use crate::translate::expr::maybe_apply_affinity;
use crate::translate::expr::translate_expr;
use crate::translate::normalize_ident;
use crate::translate::where_clause::{
process_where, translate_processed_where, translate_where, ProcessedWhereClause,
};
use crate::translate::{Insn, LimitInfo};
use crate::types::{OwnedRecord, OwnedValue};
use crate::vdbe::{builder::ProgramBuilder, Program};
use crate::{function::Func, schema::Table, vdbe::BranchOffset};
use super::SortInfo;
#[derive(Debug)]
pub struct SrcTable<'a> {
pub table: Table,
pub identifier: String,
pub join_info: Option<&'a ast::JoinedSelectTable>,
}
impl SrcTable<'_> {
pub fn is_outer_join(&self) -> bool {
if let Some(ast::JoinedSelectTable {
operator: JoinOperator::TypedJoin(Some(join_type)),
..
}) = self.join_info
{
if *join_type == JoinType::LEFT | JoinType::OUTER {
true
} else if *join_type == JoinType::RIGHT | JoinType::OUTER {
true
} else {
false
}
} else {
false
}
}
}
#[derive(Debug)]
pub struct ColumnInfo<'a> {
pub func: Option<Func>,
pub args: &'a Option<Vec<ast::Expr>>,
pub columns_to_allocate: usize, /* number of result columns this col will result on */
}
impl<'a> ColumnInfo<'a> {
pub fn new() -> Self {
Self {
func: None,
args: &None,
columns_to_allocate: 1,
}
}
pub fn is_aggregation_function(&self) -> bool {
matches!(self.func, Some(Func::Agg(_)))
}
}
pub struct LeftJoinBookkeeping {
// integer register that holds a flag that is set to true if the current row has a match for the left join
pub match_flag_register: usize,
// label for the instruction that sets the match flag to true
pub set_match_flag_true_label: BranchOffset,
// label for the instruction where the program jumps to if the current row has a match for the left join
pub on_match_jump_to_label: BranchOffset,
}
pub struct LoopInfo {
// The table or table alias that we are looping over
pub identifier: String,
// Metadata about a left join, if any
pub left_join_bookkeeping: Option<LeftJoinBookkeeping>,
// The label for the instruction that reads the next row for this table
pub next_row_label: BranchOffset,
// The label for the instruction that rewinds the cursor for this table
pub rewind_label: BranchOffset,
// The label for the instruction that is jumped to in the Rewind instruction if the table is empty
pub rewind_on_empty_label: BranchOffset,
// The ID of the cursor that is opened for this table
pub open_cursor: usize,
}
pub struct Select<'a> {
pub columns: &'a Vec<ast::ResultColumn>,
pub column_info: Vec<ColumnInfo<'a>>,
pub src_tables: Vec<SrcTable<'a>>, // Tables we use to get data from. This includes "from" and "joins"
pub limit: &'a Option<ast::Limit>,
pub order_by: &'a Option<Vec<ast::SortedColumn>>,
pub exist_aggregation: bool,
pub where_clause: &'a Option<ast::Expr>,
/// Ordered list of opened read table loops
/// Used for generating a loop that looks like this:
/// cursor 0 = open table 0
/// for each row in cursor 0
/// cursor 1 = open table 1
/// for each row in cursor 1
/// ...
/// end cursor 1
/// end cursor 0
pub loops: Vec<LoopInfo>,
}
pub fn build_select<'a>(schema: &Schema, select: &'a ast::Select) -> Result<Select<'a>> {
match &select.body.select {
ast::OneSelect::Select {
columns,
from: Some(from),
where_clause,
..
} => {
let (table_name, maybe_alias) = match &from.select {
Some(select_table) => match select_table.as_ref() {
ast::SelectTable::Table(name, alias, ..) => (
&name.name,
alias.as_ref().map(|als| match als {
ast::As::As(alias) => alias, // users as u
ast::As::Elided(alias) => alias, // users u
}),
),
_ => todo!(),
},
None => todo!(),
};
let table_name = &table_name.0;
let maybe_alias = maybe_alias.map(|als| &als.0);
let table = match schema.get_table(table_name) {
Some(table) => table,
None => anyhow::bail!("Parse error: no such table: {}", table_name),
};
let identifier = normalize_ident(maybe_alias.unwrap_or(table_name));
let mut joins = Vec::new();
joins.push(SrcTable {
table: Table::BTree(table.clone()),
identifier,
join_info: None,
});
if let Some(selected_joins) = &from.joins {
for join in selected_joins {
let (table_name, maybe_alias) = match &join.table {
ast::SelectTable::Table(name, alias, ..) => (
&name.name,
alias.as_ref().map(|als| match als {
ast::As::As(alias) => alias, // users as u
ast::As::Elided(alias) => alias, // users u
}),
),
_ => todo!(),
};
let table_name = &table_name.0;
let maybe_alias = maybe_alias.as_ref().map(|als| &als.0);
let table = match schema.get_table(table_name) {
Some(table) => table,
None => anyhow::bail!("Parse error: no such table: {}", table_name),
};
let identifier = normalize_ident(maybe_alias.unwrap_or(table_name));
joins.push(SrcTable {
table: Table::BTree(table),
identifier,
join_info: Some(join),
});
}
}
let _table = Table::BTree(table);
let column_info = analyze_columns(columns, &joins);
let exist_aggregation = column_info
.iter()
.any(|info| info.is_aggregation_function());
Ok(Select {
columns,
column_info,
src_tables: joins,
limit: &select.limit,
order_by: &select.order_by,
exist_aggregation,
where_clause,
loops: Vec::new(),
})
}
ast::OneSelect::Select {
columns,
from: None,
where_clause,
..
} => {
let column_info = analyze_columns(columns, &Vec::new());
let exist_aggregation = column_info
.iter()
.any(|info| info.is_aggregation_function());
Ok(Select {
columns,
column_info,
src_tables: Vec::new(),
limit: &select.limit,
order_by: &select.order_by,
where_clause,
exist_aggregation,
loops: Vec::new(),
})
}
_ => todo!(),
}
}
/// Generate code for a SELECT statement.
pub fn translate_select(mut select: Select) -> Result<Program> {
let mut program = ProgramBuilder::new();
let init_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::Init {
target_pc: init_label,
},
init_label,
);
let start_offset = program.offset();
let mut sort_info = if let Some(order_by) = select.order_by {
let sorter_cursor = program.alloc_cursor_id(None, None);
let mut order = Vec::new();
for col in order_by {
order.push(OwnedValue::Integer(if let Some(ord) = col.order {
ord as i64
} else {
0
}));
}
program.emit_insn(Insn::SorterOpen {
cursor_id: sorter_cursor,
order: OwnedRecord::new(order),
columns: select.column_info.len() + 1, // +1 for the key
});
Some(SortInfo {
sorter_cursor,
sorter_reg: 0, // will be overwritten later
count: 0, // will be overwritten later
})
} else {
None
};
let limit_info = if let Some(limit) = &select.limit {
assert!(limit.offset.is_none());
let target_register = program.alloc_register();
let limit_reg = translate_expr(&mut program, &select, &limit.expr, target_register, None)?;
let num = if let ast::Expr::Literal(ast::Literal::Numeric(num)) = &limit.expr {
num.parse::<i64>()?
} else {
todo!();
};
let goto_label = program.allocate_label();
if num == 0 {
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: goto_label,
},
goto_label,
);
}
Some(LimitInfo {
limit_reg,
num,
goto_label,
})
} else {
None
};
if !select.src_tables.is_empty() {
translate_tables_begin(&mut program, &mut select)?;
let (register_start, column_count) = if let Some(sort_columns) = select.order_by {
let start = program.next_free_register();
for col in sort_columns.iter() {
let target = program.alloc_register();
translate_expr(&mut program, &select, &col.expr, target, None)?;
}
let (_, result_cols_count) = translate_columns(&mut program, &select, None)?;
sort_info
.as_mut()
.map(|inner| inner.count = result_cols_count + sort_columns.len() + 1); // +1 for the key
(start, result_cols_count + sort_columns.len())
} else {
translate_columns(&mut program, &select, None)?
};
if !select.exist_aggregation {
if let Some(ref mut sort_info) = sort_info {
let dest = program.alloc_register();
program.emit_insn(Insn::MakeRecord {
start_reg: register_start,
count: column_count,
dest_reg: dest,
});
program.emit_insn(Insn::SorterInsert {
cursor_id: sort_info.sorter_cursor,
record_reg: dest,
});
sort_info.sorter_reg = register_start;
} else {
program.emit_insn(Insn::ResultRow {
start_reg: register_start,
count: column_count,
});
emit_limit_insn(&limit_info, &mut program);
}
}
translate_tables_end(&mut program, &select);
if select.exist_aggregation {
let mut target = register_start;
for info in &select.column_info {
if let Some(Func::Agg(func)) = &info.func {
program.emit_insn(Insn::AggFinal {
register: target,
func: func.clone(),
});
}
target += info.columns_to_allocate;
}
// only one result row
program.emit_insn(Insn::ResultRow {
start_reg: register_start,
count: column_count,
});
emit_limit_insn(&limit_info, &mut program);
}
} else {
assert!(!select.exist_aggregation);
assert!(sort_info.is_none());
let where_maybe = translate_where(&select, &mut program)?;
let (register_start, count) = translate_columns(&mut program, &select, None)?;
if let Some(where_clause_label) = where_maybe {
program.resolve_label(where_clause_label, program.offset() + 1);
}
program.emit_insn(Insn::ResultRow {
start_reg: register_start,
count,
});
emit_limit_insn(&limit_info, &mut program);
};
// now do the sort for ORDER BY
if select.order_by.is_some() {
let _ = translate_sorter(&select, &mut program, &sort_info.unwrap(), &limit_info);
}
program.emit_insn(Insn::Halt);
let halt_offset = program.offset() - 1;
if let Some(limit_info) = limit_info {
if limit_info.goto_label < 0 {
program.resolve_label(limit_info.goto_label, halt_offset);
}
}
program.resolve_label(init_label, program.offset());
program.emit_insn(Insn::Transaction);
program.emit_constant_insns();
program.emit_insn(Insn::Goto {
target_pc: start_offset,
});
program.resolve_deferred_labels();
Ok(program.build())
}
fn emit_limit_insn(limit_info: &Option<LimitInfo>, program: &mut ProgramBuilder) {
if limit_info.is_none() {
return;
}
let limit_info = limit_info.as_ref().unwrap();
if limit_info.num > 0 {
program.emit_insn_with_label_dependency(
Insn::DecrJumpZero {
reg: limit_info.limit_reg,
target_pc: limit_info.goto_label,
},
limit_info.goto_label,
);
}
}
fn translate_sorter(
select: &Select,
program: &mut ProgramBuilder,
sort_info: &SortInfo,
limit_info: &Option<LimitInfo>,
) -> Result<()> {
assert!(sort_info.count > 0);
let mut pseudo_columns = Vec::new();
for col in select.columns.iter() {
match col {
ast::ResultColumn::Expr(expr, _) => match expr {
ast::Expr::Id(ident) => {
pseudo_columns.push(Column {
name: normalize_ident(&ident.0),
primary_key: false,
ty: crate::schema::Type::Null,
});
}
ast::Expr::Qualified(table_name, ident) => {
pseudo_columns.push(Column {
name: normalize_ident(format!("{}.{}", table_name.0, ident.0).as_str()),
primary_key: false,
ty: crate::schema::Type::Null,
});
}
other => {
todo!("translate_sorter: {:?}", other);
}
},
ast::ResultColumn::Star => {}
ast::ResultColumn::TableStar(_) => {}
}
}
let pseudo_cursor = program.alloc_cursor_id(
None,
Some(Table::Pseudo(Rc::new(PseudoTable {
columns: pseudo_columns,
}))),
);
let pseudo_content_reg = program.alloc_register();
program.emit_insn(Insn::OpenPseudo {
cursor_id: pseudo_cursor,
content_reg: pseudo_content_reg,
num_fields: sort_info.count,
});
let label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::SorterSort {
cursor_id: sort_info.sorter_cursor,
pc_if_empty: label,
},
label,
);
let sorter_data_offset = program.offset();
program.emit_insn(Insn::SorterData {
cursor_id: sort_info.sorter_cursor,
dest_reg: pseudo_content_reg,
pseudo_cursor,
});
let (register_start, count) = translate_columns(program, select, Some(pseudo_cursor))?;
program.emit_insn(Insn::ResultRow {
start_reg: register_start,
count,
});
emit_limit_insn(limit_info, program);
program.emit_insn(Insn::SorterNext {
cursor_id: sort_info.sorter_cursor,
pc_if_next: sorter_data_offset,
});
program.resolve_label(label, program.offset());
Ok(())
}
fn translate_tables_begin(program: &mut ProgramBuilder, select: &mut Select) -> Result<()> {
for join in &select.src_tables {
let loop_info = translate_table_open_cursor(program, join);
select.loops.push(loop_info);
}
let processed_where = process_where(program, select)?;
for loop_info in &select.loops {
translate_table_open_loop(program, select, loop_info, &processed_where)?;
}
Ok(())
}
fn translate_tables_end(program: &mut ProgramBuilder, select: &Select) {
// iterate in reverse order as we open cursors in order
for table_loop in select.loops.iter().rev() {
let cursor_id = table_loop.open_cursor;
program.resolve_label(table_loop.next_row_label, program.offset());
program.emit_insn(Insn::NextAsync { cursor_id });
program.emit_insn_with_label_dependency(
Insn::NextAwait {
cursor_id,
pc_if_next: table_loop.rewind_label,
},
table_loop.rewind_label,
);
if let Some(ljbk) = &table_loop.left_join_bookkeeping {
left_join_match_flag_check(program, ljbk, cursor_id);
}
}
}
fn translate_table_open_cursor(program: &mut ProgramBuilder, table: &SrcTable) -> LoopInfo {
let cursor_id =
program.alloc_cursor_id(Some(table.identifier.clone()), Some(table.table.clone()));
let root_page = match &table.table {
Table::BTree(btree) => btree.root_page,
Table::Pseudo(_) => todo!(),
};
program.emit_insn(Insn::OpenReadAsync {
cursor_id,
root_page,
});
program.emit_insn(Insn::OpenReadAwait);
LoopInfo {
identifier: table.identifier.clone(),
left_join_bookkeeping: if table.is_outer_join() {
Some(LeftJoinBookkeeping {
match_flag_register: program.alloc_register(),
on_match_jump_to_label: program.allocate_label(),
set_match_flag_true_label: program.allocate_label(),
})
} else {
None
},
open_cursor: cursor_id,
next_row_label: program.allocate_label(),
rewind_label: program.allocate_label(),
rewind_on_empty_label: program.allocate_label(),
}
}
/**
* initialize left join match flag to false
* if condition checks pass, it will eventually be set to true
*/
fn left_join_match_flag_initialize(program: &mut ProgramBuilder, ljbk: &LeftJoinBookkeeping) {
program.emit_insn(Insn::Integer {
value: 0,
dest: ljbk.match_flag_register,
});
}
/**
* after the relevant conditional jumps have been emitted, set the left join match flag to true
*/
fn left_join_match_flag_set_true(program: &mut ProgramBuilder, ljbk: &LeftJoinBookkeeping) {
program.defer_label_resolution(ljbk.set_match_flag_true_label, program.offset() as usize);
program.emit_insn(Insn::Integer {
value: 1,
dest: ljbk.match_flag_register,
});
}
/**
* check if the left join match flag is set to true
* if it is, jump to the next row on the outer table
* if not, set the right table cursor's "pseudo null bit" on
* then jump to setting the left join match flag to true again,
* which will effectively emit all nulls for the right table.
*/
fn left_join_match_flag_check(
program: &mut ProgramBuilder,
ljbk: &LeftJoinBookkeeping,
cursor_id: usize,
) {
// If the left join match flag has been set to 1, we jump to the next row on the outer table (result row has been emitted already)
program.emit_insn_with_label_dependency(
Insn::IfPos {
reg: ljbk.match_flag_register,
target_pc: ljbk.on_match_jump_to_label,
decrement_by: 0,
},
ljbk.on_match_jump_to_label,
);
// If not, we set the right table cursor's "pseudo null bit" on, which means any Insn::Column will return NULL
program.emit_insn(Insn::NullRow { cursor_id });
// Jump to setting the left join match flag to 1 again, but this time the right table cursor will set everything to null
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: ljbk.set_match_flag_true_label,
},
ljbk.set_match_flag_true_label,
);
// This points to the NextAsync instruction of the next table in the loop
// (i.e. the outer table, since we're iterating in reverse order)
program.resolve_label(ljbk.on_match_jump_to_label, program.offset());
}
fn translate_table_open_loop(
program: &mut ProgramBuilder,
select: &Select,
loop_info: &LoopInfo,
w: &ProcessedWhereClause,
) -> Result<()> {
if let Some(ljbk) = loop_info.left_join_bookkeeping.as_ref() {
left_join_match_flag_initialize(program, ljbk);
}
program.emit_insn(Insn::RewindAsync {
cursor_id: loop_info.open_cursor,
});
program.defer_label_resolution(loop_info.rewind_label, program.offset() as usize);
program.emit_insn_with_label_dependency(
Insn::RewindAwait {
cursor_id: loop_info.open_cursor,
pc_if_empty: loop_info.rewind_on_empty_label,
},
loop_info.rewind_on_empty_label,
);
translate_processed_where(program, select, loop_info, w, None)?;
if let Some(ljbk) = loop_info.left_join_bookkeeping.as_ref() {
left_join_match_flag_set_true(program, ljbk);
}
Ok(())
}
fn translate_columns(
program: &mut ProgramBuilder,
select: &Select,
cursor_hint: Option<usize>,
) -> Result<(usize, usize)> {
let register_start = program.next_free_register();
// allocate one register as output for each col
let registers: usize = select
.column_info
.iter()
.map(|col| col.columns_to_allocate)
.sum();
program.alloc_registers(registers);
let count = program.next_free_register() - register_start;
let mut target = register_start;
for (col, info) in select.columns.iter().zip(select.column_info.iter()) {
translate_column(program, select, col, info, target, cursor_hint)?;
target += info.columns_to_allocate;
}
Ok((register_start, count))
}
fn translate_column(
program: &mut ProgramBuilder,
select: &Select,
col: &ast::ResultColumn,
info: &ColumnInfo,
target_register: usize, // where to store the result, in case of star it will be the start of registers added
cursor_hint: Option<usize>,
) -> Result<()> {
match col {
ast::ResultColumn::Expr(expr, _) => {
if info.is_aggregation_function() {
let _ = translate_aggregation(
program,
select,
expr,
info,
target_register,
cursor_hint,
)?;
} else {
let _ = translate_expr(program, select, expr, target_register, cursor_hint)?;
}
}
ast::ResultColumn::Star => {
let mut target_register = target_register;
for join in &select.src_tables {
translate_table_star(join, program, target_register, cursor_hint);
target_register += &join.table.columns().len();
}
}
ast::ResultColumn::TableStar(_) => todo!(),
}
Ok(())
}
fn translate_table_star(
table: &SrcTable,
program: &mut ProgramBuilder,
target_register: usize,
cursor_hint: Option<usize>,
) {
let table_cursor = program.resolve_cursor_id(&table.identifier, cursor_hint);
let table = &table.table;
for (i, col) in table.columns().iter().enumerate() {
let col_target_register = target_register + i;
if table.column_is_rowid_alias(col) {
program.emit_insn(Insn::RowId {
cursor_id: table_cursor,
dest: col_target_register,
});
} else {
program.emit_insn(Insn::Column {
column: i,
dest: col_target_register,
cursor_id: table_cursor,
});
maybe_apply_affinity(col.ty, col_target_register, program);
}
}
}
fn translate_aggregation(
program: &mut ProgramBuilder,
select: &Select,
expr: &ast::Expr,
info: &ColumnInfo,
target_register: usize,
cursor_hint: Option<usize>,
) -> Result<usize> {
let _ = expr;
assert!(info.func.is_some());
let func = info.func.as_ref().unwrap();
let empty_args = &Vec::<ast::Expr>::new();
let args = info.args.as_ref().unwrap_or(empty_args);
let dest = match func {
Func::Scalar(_) => anyhow::bail!("Parse error: single row function in aggregation"),
Func::Agg(agg_func) => match agg_func {
AggFunc::Avg => {
if args.len() != 1 {
anyhow::bail!("Parse error: avg bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, select, expr, expr_reg, cursor_hint)?;
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 args.is_empty() {
program.alloc_register()
} else {
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, select, expr, expr_reg, cursor_hint);
expr_reg
};
program.emit_insn(Insn::AggStep {
acc_reg: target_register,
col: expr_reg,
delimiter: 0,
func: AggFunc::Count,
});
target_register
}
AggFunc::GroupConcat => {
if args.len() != 1 && args.len() != 2 {
anyhow::bail!("Parse error: group_concat bad number of arguments");
}
let expr_reg = program.alloc_register();
let delimiter_reg = program.alloc_register();
let expr = &args[0];
let delimiter_expr: ast::Expr;
if args.len() == 2 {
match &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 = args[1].clone();
}
}
ast::Expr::Literal(ast::Literal::String(s)) => {
delimiter_expr =
ast::Expr::Literal(ast::Literal::String(s.to_string()));
}
_ => anyhow::bail!("Incorrect delimiter parameter"),
};
} else {
delimiter_expr =
ast::Expr::Literal(ast::Literal::String(String::from("\",\"")));
}
if let Err(error) = translate_expr(program, select, expr, expr_reg, cursor_hint) {
anyhow::bail!(error);
}
if let Err(error) =
translate_expr(program, select, &delimiter_expr, delimiter_reg, cursor_hint)
{
anyhow::bail!(error);
}
program.emit_insn(Insn::AggStep {
acc_reg: target_register,
col: expr_reg,
delimiter: delimiter_reg,
func: AggFunc::GroupConcat,
});
target_register
}
AggFunc::Max => {
if args.len() != 1 {
anyhow::bail!("Parse error: max bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, select, expr, expr_reg, cursor_hint);
program.emit_insn(Insn::AggStep {
acc_reg: target_register,
col: expr_reg,
delimiter: 0,
func: AggFunc::Max,
});
target_register
}
AggFunc::Min => {
if args.len() != 1 {
anyhow::bail!("Parse error: min bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, select, expr, expr_reg, cursor_hint);
program.emit_insn(Insn::AggStep {
acc_reg: target_register,
col: expr_reg,
delimiter: 0,
func: AggFunc::Min,
});
target_register
}
AggFunc::StringAgg => {
if args.len() != 2 {
anyhow::bail!("Parse error: string_agg bad number of arguments");
}
let expr_reg = program.alloc_register();
let delimiter_reg = program.alloc_register();
let expr = &args[0];
let delimiter_expr: ast::Expr;
match &args[1] {
ast::Expr::Id(ident) => {
if ident.0.starts_with('"') {
anyhow::bail!("Parse error: no such column: \",\" - should this be a string literal in single-quotes?");
} else {
delimiter_expr = args[1].clone();
}
}
ast::Expr::Literal(ast::Literal::String(s)) => {
delimiter_expr = ast::Expr::Literal(ast::Literal::String(s.to_string()));
}
_ => anyhow::bail!("Incorrect delimiter parameter"),
};
if let Err(error) = translate_expr(program, select, expr, expr_reg, cursor_hint) {
anyhow::bail!(error);
}
if let Err(error) =
translate_expr(program, select, &delimiter_expr, delimiter_reg, cursor_hint)
{
anyhow::bail!(error);
}
program.emit_insn(Insn::AggStep {
acc_reg: target_register,
col: expr_reg,
delimiter: delimiter_reg,
func: AggFunc::StringAgg,
});
target_register
}
AggFunc::Sum => {
if args.len() != 1 {
anyhow::bail!("Parse error: sum bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, select, expr, expr_reg, cursor_hint)?;
program.emit_insn(Insn::AggStep {
acc_reg: target_register,
col: expr_reg,
delimiter: 0,
func: AggFunc::Sum,
});
target_register
}
AggFunc::Total => {
if args.len() != 1 {
anyhow::bail!("Parse error: total bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, select, expr, expr_reg, cursor_hint)?;
program.emit_insn(Insn::AggStep {
acc_reg: target_register,
col: expr_reg,
delimiter: 0,
func: AggFunc::Total,
});
target_register
}
},
};
Ok(dest)
}