Files
turso/core/translate/select.rs
2024-07-31 17:27:02 +02:00

1004 lines
36 KiB
Rust

use crate::function::{AggFunc, Func};
use crate::schema::{Column, PseudoTable, Schema, Table};
use crate::sqlite3_ondisk::DatabaseHeader;
use crate::translate::expr::{analyze_columns, maybe_apply_affinity, translate_expr};
use crate::translate::where_clause::{
process_where, translate_processed_where, translate_tableless_where, ProcessedWhereClause,
};
use crate::translate::{normalize_ident, Insn};
use crate::types::{OwnedRecord, OwnedValue};
use crate::vdbe::{builder::ProgramBuilder, BranchOffset, Program};
use crate::Result;
use sqlite3_parser::ast::{self, JoinOperator, JoinType, ResultColumn};
use std::cell::RefCell;
use std::rc::Rc;
/// A representation of a `SELECT` statement that has all the information
/// needed for code generation.
pub struct Select<'a> {
/// Information about each column.
pub column_info: Vec<ColumnInfo<'a>>,
/// The tables we are retrieving data from, including tables mentioned
/// in `FROM` and `JOIN` clauses.
pub src_tables: Vec<SrcTable<'a>>,
/// The `LIMIT` clause.
pub limit: &'a Option<ast::Limit>,
/// The `ORDER BY` clause.
pub order_by: &'a Option<Vec<ast::SortedColumn>>,
/// Whether the query contains an aggregation function.
pub exist_aggregation: bool,
/// The `WHERE` clause.
pub where_clause: &'a Option<ast::Expr>,
}
#[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 raw_column: &'a ast::ResultColumn,
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(raw_column: &'a ast::ResultColumn) -> Self {
Self {
raw_column,
func: None,
args: &None,
columns_to_allocate: 1,
}
}
pub fn is_aggregation_function(&self) -> bool {
matches!(self.func, Some(Func::Agg(_)))
}
}
#[derive(Debug)]
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 that checks if the match flag is true
pub check_match_flag_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,
}
/// Represents a single loop in an ordered list of opened read table loops.
///
/// The list is used to generate inner loops 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
#[derive(Debug)]
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_maybe: 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,
}
struct LimitInfo {
limit_reg: usize,
num: i64,
goto_label: BranchOffset,
}
#[derive(Debug)]
struct SortInfo {
sorter_cursor: usize,
sorter_reg: usize,
count: usize,
}
pub fn prepare_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 => crate::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 => {
crate::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 {
column_info,
src_tables: joins,
limit: &select.limit,
order_by: &select.order_by,
exist_aggregation,
where_clause,
})
}
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 {
column_info,
src_tables: Vec::new(),
limit: &select.limit,
order_by: &select.order_by,
where_clause,
exist_aggregation,
})
}
_ => todo!(),
}
}
/// Generate code for a SELECT statement.
pub fn translate_select(
mut select: Select,
database_header: Rc<RefCell<DatabaseHeader>>,
) -> Result<Program> {
let mut program = ProgramBuilder::new();
let init_label = program.allocate_label();
let early_terminate_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,
Some(&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() {
let loops = translate_tables_begin(&mut program, &mut select, early_terminate_label)?;
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();
// if the ORDER BY expression is a number, interpret it as an 1-indexed column number
// otherwise, interpret it normally as an expression
let sort_col_expr = if let ast::Expr::Literal(ast::Literal::Numeric(num)) =
&col.expr
{
let column_number = num.parse::<usize>()?;
if column_number == 0 {
crate::bail_parse_error!("invalid column index: {}", column_number);
}
let maybe_result_column = select
.column_info
.get(column_number - 1)
.map(|col| &col.raw_column);
match maybe_result_column {
Some(ResultColumn::Expr(expr, _)) => expr,
None => crate::bail_parse_error!("invalid column index: {}", column_number),
_ => todo!(),
}
} else {
&col.expr
};
translate_expr(&mut program, Some(&select), sort_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, &loops);
if select.exist_aggregation {
program.resolve_label(early_terminate_label, program.offset());
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_tableless_where(&select, &mut program, early_terminate_label)?;
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);
}
if !select.exist_aggregation {
program.resolve_label(early_terminate_label, program.offset());
}
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(database_header))
}
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.column_info.iter() {
match col.raw_column {
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,
early_terminate_label: BranchOffset,
) -> Result<Vec<LoopInfo>> {
let processed_where = process_where(select)?;
let mut loops = Vec::with_capacity(select.src_tables.len());
for idx in &processed_where.loop_order {
let join = select
.src_tables
.get(*idx)
.expect("loop order out of bounds");
let loop_info = translate_table_open_cursor(program, join);
loops.push(loop_info);
}
for loop_info in &loops {
// early_terminate_label decides where to jump _IF_ there exists a condition on this loop that is always false.
// this is part of a constant folding optimization where we can skip the loop entirely if we know it will never produce any rows.
let current_loop_early_terminate_label = if let Some(left_join) = &loop_info.left_join_maybe
{
// If there exists a condition on the LEFT JOIN that is always false, e.g.:
// 'SELECT * FROM x LEFT JOIN y ON false'
// then we can't jump to e.g. Halt, but instead we need to still emit all rows from the 'x' table, with NULLs for the 'y' table.
// 'check_match_flag_label' is the label that checks if the left join match flag has been set to true, and if not (which it by default isn't),
// sets the 'y' cursor's "pseudo null bit" on, which means any Insn::Column after that will return NULL for the 'y' table.
left_join.check_match_flag_label
} else {
// If there exists a condition in an INNER JOIN (or WHERE) that is always false, then the query will not produce any rows.
// Example: 'SELECT * FROM x JOIN y ON false' or 'SELECT * FROM x WHERE false'
// Here we should jump to Halt (or e.g. AggFinal in case we have an aggregation expression like count() that should produce a 0 on empty input.
early_terminate_label
};
translate_table_open_loop(
program,
select,
loop_info,
&processed_where,
current_loop_early_terminate_label,
)?;
}
Ok(loops)
}
fn translate_tables_end(program: &mut ProgramBuilder, loops: &[LoopInfo]) {
// iterate in reverse order as we open cursors in order
for table_loop in 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(left_join) = &table_loop.left_join_maybe {
left_join_match_flag_check(program, left_join, 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_maybe: if table.is_outer_join() {
Some(LeftJoinBookkeeping {
match_flag_register: program.alloc_register(),
on_match_jump_to_label: program.allocate_label(),
check_match_flag_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, left_join: &LeftJoinBookkeeping) {
program.emit_insn(Insn::Integer {
value: 0,
dest: left_join.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, left_join: &LeftJoinBookkeeping) {
program.defer_label_resolution(
left_join.set_match_flag_true_label,
program.offset() as usize,
);
program.emit_insn(Insn::Integer {
value: 1,
dest: left_join.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,
left_join: &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.resolve_label(left_join.check_match_flag_label, program.offset());
program.emit_insn_with_label_dependency(
Insn::IfPos {
reg: left_join.match_flag_register,
target_pc: left_join.on_match_jump_to_label,
decrement_by: 0,
},
left_join.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: left_join.set_match_flag_true_label,
},
left_join.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(left_join.on_match_jump_to_label, program.offset());
}
fn translate_table_open_loop(
program: &mut ProgramBuilder,
select: &Select,
loop_info: &LoopInfo,
w: &ProcessedWhereClause,
early_terminate_label: BranchOffset,
) -> Result<()> {
if let Some(left_join) = loop_info.left_join_maybe.as_ref() {
left_join_match_flag_initialize(program, left_join);
}
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, early_terminate_label, None)?;
if let Some(left_join) = loop_info.left_join_maybe.as_ref() {
left_join_match_flag_set_true(program, left_join);
}
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 info in select.column_info.iter() {
translate_column(program, select, info.raw_column, 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, Some(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(_) => {
crate::bail_parse_error!("single row function in aggregation")
}
Func::Agg(agg_func) => match agg_func {
AggFunc::Avg => {
if args.len() != 1 {
crate::bail_parse_error!("avg bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, Some(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, Some(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 {
crate::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()));
}
_ => crate::bail_parse_error!("Incorrect delimiter parameter"),
};
} else {
delimiter_expr =
ast::Expr::Literal(ast::Literal::String(String::from("\",\"")));
}
translate_expr(program, Some(select), expr, expr_reg, cursor_hint)?;
translate_expr(
program,
Some(select),
&delimiter_expr,
delimiter_reg,
cursor_hint,
)?;
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 {
crate::bail_parse_error!("max bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, Some(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 {
crate::bail_parse_error!("min bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, Some(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 {
crate::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('"') {
crate::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()));
}
_ => crate::bail_parse_error!("Incorrect delimiter parameter"),
};
translate_expr(program, Some(select), expr, expr_reg, cursor_hint)?;
translate_expr(
program,
Some(select),
&delimiter_expr,
delimiter_reg,
cursor_hint,
)?;
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 {
crate::bail_parse_error!("sum bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, Some(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 {
crate::bail_parse_error!("total bad number of arguments");
}
let expr = &args[0];
let expr_reg = program.alloc_register();
let _ = translate_expr(program, Some(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)
}