mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-18 17:14:20 +01:00
Adds initial support for window functions. For now, only existing aggregate functions can be used as window functions—no specialized window-specific functions are supported yet. Currently, only the default frame definition is implemented: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS.
140 lines
5.7 KiB
Rust
140 lines
5.7 KiB
Rust
use crate::{
|
|
emit_explain,
|
|
schema::Table,
|
|
vdbe::{builder::ProgramBuilder, insn::Insn},
|
|
QueryMode, Result,
|
|
};
|
|
|
|
use super::{
|
|
emitter::{emit_query, Resolver, TranslateCtx},
|
|
main_loop::LoopLabels,
|
|
plan::{Operation, QueryDestination, Search, SelectPlan, TableReferences},
|
|
};
|
|
|
|
/// Emit the subqueries contained in the FROM clause.
|
|
/// This is done first so the results can be read in the main query loop.
|
|
pub fn emit_subqueries(
|
|
program: &mut ProgramBuilder,
|
|
t_ctx: &mut TranslateCtx,
|
|
tables: &mut TableReferences,
|
|
) -> Result<()> {
|
|
if tables.joined_tables().is_empty() {
|
|
emit_explain!(program, false, "SCAN CONSTANT ROW".to_owned());
|
|
}
|
|
|
|
for table_reference in tables.joined_tables_mut() {
|
|
emit_explain!(
|
|
program,
|
|
true,
|
|
match &table_reference.op {
|
|
Operation::Scan { .. } => {
|
|
if table_reference.table.get_name() == table_reference.identifier {
|
|
format!("SCAN {}", table_reference.identifier)
|
|
} else {
|
|
format!(
|
|
"SCAN {} AS {}",
|
|
table_reference.table.get_name(),
|
|
table_reference.identifier
|
|
)
|
|
}
|
|
}
|
|
Operation::Search(search) => match search {
|
|
Search::RowidEq { .. } | Search::Seek { index: None, .. } => {
|
|
format!(
|
|
"SEARCH {} USING INTEGER PRIMARY KEY (rowid=?)",
|
|
table_reference.identifier
|
|
)
|
|
}
|
|
Search::Seek {
|
|
index: Some(index), ..
|
|
} => {
|
|
format!(
|
|
"SEARCH {} USING INDEX {}",
|
|
table_reference.identifier, index.name
|
|
)
|
|
}
|
|
},
|
|
}
|
|
);
|
|
|
|
if let Table::FromClauseSubquery(from_clause_subquery) = &mut table_reference.table {
|
|
// Emit the subquery and get the start register of the result columns.
|
|
let result_columns_start =
|
|
emit_subquery(program, &mut from_clause_subquery.plan, t_ctx)?;
|
|
// Set the start register of the subquery's result columns.
|
|
// This is done so that translate_expr() can read the result columns of the subquery,
|
|
// as if it were reading from a regular table.
|
|
from_clause_subquery.result_columns_start_reg = Some(result_columns_start);
|
|
}
|
|
|
|
program.pop_current_parent_explain();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Emit a subquery and return the start register of the result columns.
|
|
/// This is done by emitting a coroutine that stores the result columns in sequential registers.
|
|
/// Each subquery in a FROM clause has its own separate SelectPlan which is wrapped in a coroutine.
|
|
///
|
|
/// The resulting bytecode from a subquery is mostly exactly the same as a regular query, except:
|
|
/// - it ends in an EndCoroutine instead of a Halt.
|
|
/// - instead of emitting ResultRows, the coroutine yields to the main query loop.
|
|
/// - the first register of the result columns is returned to the parent query,
|
|
/// so that translate_expr() can read the result columns of the subquery,
|
|
/// as if it were reading from a regular table.
|
|
///
|
|
/// Since a subquery has its own SelectPlan, it can contain nested subqueries,
|
|
/// which can contain even more nested subqueries, etc.
|
|
pub fn emit_subquery(
|
|
program: &mut ProgramBuilder,
|
|
plan: &mut SelectPlan,
|
|
t_ctx: &mut TranslateCtx,
|
|
) -> Result<usize> {
|
|
let yield_reg = program.alloc_register();
|
|
let coroutine_implementation_start_offset = program.allocate_label();
|
|
match &mut plan.query_destination {
|
|
QueryDestination::CoroutineYield {
|
|
yield_reg: y,
|
|
coroutine_implementation_start,
|
|
} => {
|
|
// The parent query will use this register to jump to/from the subquery.
|
|
*y = yield_reg;
|
|
// The parent query will use this register to reinitialize the coroutine when it needs to run multiple times.
|
|
*coroutine_implementation_start = coroutine_implementation_start_offset;
|
|
}
|
|
_ => unreachable!("emit_subquery called on non-subquery"),
|
|
}
|
|
let end_coroutine_label = program.allocate_label();
|
|
let mut metadata = TranslateCtx {
|
|
labels_main_loop: (0..plan.joined_tables().len())
|
|
.map(|_| LoopLabels::new(program))
|
|
.collect(),
|
|
label_main_loop_end: None,
|
|
meta_group_by: None,
|
|
meta_left_joins: (0..plan.joined_tables().len()).map(|_| None).collect(),
|
|
meta_sort: None,
|
|
reg_agg_start: None,
|
|
reg_nonagg_emit_once_flag: None,
|
|
reg_result_cols_start: None,
|
|
limit_ctx: None,
|
|
reg_offset: None,
|
|
reg_limit_offset_sum: None,
|
|
resolver: Resolver::new(t_ctx.resolver.schema, t_ctx.resolver.symbol_table),
|
|
non_aggregate_expressions: Vec::new(),
|
|
cdc_cursor_id: None,
|
|
meta_window: None,
|
|
};
|
|
let subquery_body_end_label = program.allocate_label();
|
|
program.emit_insn(Insn::InitCoroutine {
|
|
yield_reg,
|
|
jump_on_definition: subquery_body_end_label,
|
|
start_offset: coroutine_implementation_start_offset,
|
|
});
|
|
program.preassign_label_to_next_insn(coroutine_implementation_start_offset);
|
|
let result_column_start_reg = emit_query(program, plan, &mut metadata)?;
|
|
program.resolve_label(end_coroutine_label, program.offset());
|
|
program.emit_insn(Insn::EndCoroutine { yield_reg });
|
|
program.preassign_label_to_next_insn(subquery_body_end_label);
|
|
Ok(result_column_start_reg)
|
|
}
|