mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 00:45:37 +01:00
replace termination_label_stack with much simpler LoopLabels
This commit is contained in:
@@ -74,25 +74,30 @@ pub struct GroupByMetadata {
|
||||
pub group_exprs_comparison_register: usize,
|
||||
}
|
||||
|
||||
/// Jump labels for each loop in the query's main execution loop
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LoopLabels {
|
||||
/// jump to the start of the loop body
|
||||
loop_start: BranchOffset,
|
||||
/// jump to the NextAsync instruction (or equivalent)
|
||||
next: BranchOffset,
|
||||
/// jump to the end of the loop, exiting it
|
||||
loop_end: BranchOffset,
|
||||
}
|
||||
|
||||
/// The Metadata struct holds various information and labels used during bytecode generation.
|
||||
/// It is used for maintaining state and control flow during the bytecode
|
||||
/// generation process.
|
||||
#[derive(Debug)]
|
||||
pub struct Metadata {
|
||||
// this stack is generically used for a "jump to the end of the current query phase" purpose in a FIFO manner.
|
||||
// For example, in a nested loop join, each loop will have a label signifying the end of that particular loop.
|
||||
termination_label_stack: Vec<BranchOffset>,
|
||||
// labels for the instructions that either:
|
||||
// - jump to the start of the current loop. (e.g. a Next instruction jumps here)
|
||||
// - jump to the Next instruction (or equivalent) in the current operator. (e.g. a condition evaluates to false, so the current row is skipped)
|
||||
// - jump to the end of the current loop. (e.g. an index seek results in no key matching the seek condition, so execution will jump to the end of the loop)
|
||||
loop_labels: HashMap<usize, LoopLabels>,
|
||||
// label for the instruction that jumps to the next phase of the query after the main loop
|
||||
// we don't know ahead of time what that is (GROUP BY, ORDER BY, etc.)
|
||||
after_main_loop_label: Option<BranchOffset>,
|
||||
// labels for the instructions that jump to the next row in the current operator.
|
||||
// for example, in a join with two nested scans, the inner loop will jump to its Next instruction when the join condition is false;
|
||||
// in a join with a scan and a seek, the seek will jump to the scan's Next instruction when the join condition is false.
|
||||
// The difference between next_row_labels and termination_label_stack is that next_row_labels are used to jump to the next row in the
|
||||
// current loop, whereas termination_label_stack is used to jump OUT of the current loop entirely.
|
||||
next_row_labels: HashMap<usize, BranchOffset>,
|
||||
// labels for the instructions beginning the inner loop of a scan operator.
|
||||
scan_loop_body_labels: Vec<BranchOffset>,
|
||||
// metadata for the group by operator
|
||||
group_by_metadata: Option<GroupByMetadata>,
|
||||
// metadata for the order by operator
|
||||
@@ -139,12 +144,10 @@ fn prologue() -> Result<(ProgramBuilder, Metadata, BranchOffset, BranchOffset)>
|
||||
let start_offset = program.offset();
|
||||
|
||||
let metadata = Metadata {
|
||||
termination_label_stack: vec![halt_label],
|
||||
loop_labels: HashMap::new(),
|
||||
after_main_loop_label: None,
|
||||
group_by_metadata: None,
|
||||
left_joins: HashMap::new(),
|
||||
next_row_labels: HashMap::new(),
|
||||
scan_loop_body_labels: vec![],
|
||||
sort_metadata: None,
|
||||
aggregation_start_register: None,
|
||||
result_column_start_register: None,
|
||||
@@ -161,12 +164,9 @@ fn prologue() -> Result<(ProgramBuilder, Metadata, BranchOffset, BranchOffset)>
|
||||
/// query will jump to the Transaction instruction via init_label.
|
||||
fn epilogue(
|
||||
program: &mut ProgramBuilder,
|
||||
metadata: &mut Metadata,
|
||||
init_label: BranchOffset,
|
||||
start_offset: BranchOffset,
|
||||
) -> Result<()> {
|
||||
let halt_label = metadata.termination_label_stack.pop().unwrap();
|
||||
program.resolve_label(halt_label, program.offset());
|
||||
program.emit_insn(Insn::Halt {
|
||||
err_code: 0,
|
||||
description: String::new(),
|
||||
@@ -210,7 +210,7 @@ fn emit_program_for_select(
|
||||
// Trivial exit on LIMIT 0
|
||||
if let Some(limit) = plan.limit {
|
||||
if limit == 0 {
|
||||
epilogue(&mut program, &mut metadata, init_label, start_offset)?;
|
||||
epilogue(&mut program, init_label, start_offset)?;
|
||||
return Ok(program.build(database_header, connection));
|
||||
}
|
||||
}
|
||||
@@ -219,7 +219,7 @@ fn emit_program_for_select(
|
||||
emit_query(&mut program, &mut plan, &mut metadata, syms)?;
|
||||
|
||||
// Finalize program
|
||||
epilogue(&mut program, &mut metadata, init_label, start_offset)?;
|
||||
epilogue(&mut program, init_label, start_offset)?;
|
||||
|
||||
Ok(program.build(database_header, connection))
|
||||
}
|
||||
@@ -301,13 +301,10 @@ fn emit_subquery(
|
||||
}
|
||||
let end_coroutine_label = program.allocate_label();
|
||||
let mut metadata = Metadata {
|
||||
// A regular query ends in a Halt, whereas a subquery ends in an EndCoroutine.
|
||||
termination_label_stack: vec![end_coroutine_label],
|
||||
loop_labels: HashMap::new(),
|
||||
after_main_loop_label: None,
|
||||
group_by_metadata: None,
|
||||
left_joins: HashMap::new(),
|
||||
next_row_labels: HashMap::new(),
|
||||
scan_loop_body_labels: vec![],
|
||||
sort_metadata: None,
|
||||
aggregation_start_register: None,
|
||||
result_column_start_register: None,
|
||||
@@ -373,24 +370,11 @@ fn emit_query(
|
||||
|
||||
// Initialize cursors and other resources needed for query execution
|
||||
if let Some(ref mut order_by) = plan.order_by {
|
||||
let orderby_label = program.allocate_label();
|
||||
metadata.termination_label_stack.push(orderby_label);
|
||||
init_order_by(program, order_by, metadata)?;
|
||||
}
|
||||
|
||||
if let Some(ref mut group_by) = plan.group_by {
|
||||
let output_groupby_row_label = program.allocate_label();
|
||||
metadata
|
||||
.termination_label_stack
|
||||
.push(output_groupby_row_label);
|
||||
let groupby_end_label = program.allocate_label();
|
||||
metadata.termination_label_stack.push(groupby_end_label);
|
||||
init_group_by(program, group_by, &plan.aggregates, metadata)?;
|
||||
} else if !plan.aggregates.is_empty() {
|
||||
let output_aggregation_row_label = program.allocate_label();
|
||||
metadata
|
||||
.termination_label_stack
|
||||
.push(output_aggregation_row_label);
|
||||
}
|
||||
init_source(program, &plan.source, metadata, &OperationMode::SELECT)?;
|
||||
|
||||
@@ -503,7 +487,7 @@ fn emit_program_for_delete(
|
||||
program.resolve_label(after_main_loop_label, program.offset());
|
||||
|
||||
// Finalize program
|
||||
epilogue(&mut program, &mut metadata, init_label, start_offset)?;
|
||||
epilogue(&mut program, init_label, start_offset)?;
|
||||
|
||||
Ok(program.build(database_header, connection))
|
||||
}
|
||||
@@ -618,12 +602,16 @@ fn init_source(
|
||||
metadata: &mut Metadata,
|
||||
mode: &OperationMode,
|
||||
) -> Result<()> {
|
||||
let operator_id = source.id();
|
||||
let loop_labels = LoopLabels {
|
||||
next: program.allocate_label(),
|
||||
loop_start: program.allocate_label(),
|
||||
loop_end: program.allocate_label(),
|
||||
};
|
||||
metadata.loop_labels.insert(operator_id, loop_labels);
|
||||
|
||||
match source {
|
||||
SourceOperator::Subquery { id, .. } => {
|
||||
let next_row_label = program.allocate_label();
|
||||
metadata.next_row_labels.insert(*id, next_row_label);
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Subquery { .. } => Ok(()),
|
||||
SourceOperator::Join {
|
||||
id,
|
||||
left,
|
||||
@@ -645,17 +633,13 @@ fn init_source(
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Scan {
|
||||
id,
|
||||
table_reference,
|
||||
..
|
||||
table_reference, ..
|
||||
} => {
|
||||
let cursor_id = program.alloc_cursor_id(
|
||||
Some(table_reference.table_identifier.clone()),
|
||||
Some(table_reference.table.clone()),
|
||||
);
|
||||
let root_page = table_reference.table.get_root_page();
|
||||
let next_row_label = program.allocate_label();
|
||||
metadata.next_row_labels.insert(*id, next_row_label);
|
||||
|
||||
match mode {
|
||||
OperationMode::SELECT => {
|
||||
@@ -680,7 +664,6 @@ fn init_source(
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Search {
|
||||
id,
|
||||
table_reference,
|
||||
search,
|
||||
..
|
||||
@@ -690,10 +673,6 @@ fn init_source(
|
||||
Some(table_reference.table.clone()),
|
||||
);
|
||||
|
||||
let next_row_label = program.allocate_label();
|
||||
|
||||
metadata.next_row_labels.insert(*id, next_row_label);
|
||||
|
||||
match mode {
|
||||
OperationMode::SELECT => {
|
||||
program.emit_insn(Insn::OpenReadAsync {
|
||||
@@ -741,7 +720,7 @@ fn init_source(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Nothing => Ok(()),
|
||||
SourceOperator::Nothing { .. } => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,9 +734,6 @@ fn open_loop(
|
||||
metadata: &mut Metadata,
|
||||
syms: &SymbolTable,
|
||||
) -> Result<()> {
|
||||
metadata
|
||||
.termination_label_stack
|
||||
.push(program.allocate_label());
|
||||
match source {
|
||||
SourceOperator::Subquery {
|
||||
id,
|
||||
@@ -778,30 +754,23 @@ fn open_loop(
|
||||
jump_on_definition: 0,
|
||||
start_offset: coroutine_implementation_start,
|
||||
});
|
||||
let loop_body_start_label = program.allocate_label();
|
||||
metadata.scan_loop_body_labels.push(loop_body_start_label);
|
||||
program.defer_label_resolution(loop_body_start_label, program.offset() as usize);
|
||||
let loop_labels = metadata
|
||||
.loop_labels
|
||||
.get(id)
|
||||
.expect("subquery has no loop labels");
|
||||
program.defer_label_resolution(loop_labels.loop_start, program.offset() as usize);
|
||||
// A subquery within the main loop of a parent query has no cursor, so instead of advancing the cursor,
|
||||
// it emits a Yield which jumps back to the main loop of the subquery itself to retrieve the next row.
|
||||
// When the subquery coroutine completes, this instruction jumps to the label at the top of the termination_label_stack,
|
||||
// which in this case is the end of the Yield-Goto loop in the parent query.
|
||||
let end_of_loop_label = *metadata.termination_label_stack.last().unwrap();
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Yield {
|
||||
yield_reg,
|
||||
end_offset: end_of_loop_label,
|
||||
end_offset: loop_labels.loop_end,
|
||||
},
|
||||
end_of_loop_label,
|
||||
loop_labels.loop_end,
|
||||
);
|
||||
|
||||
// In case we have predicates on the subquery results that evaluate to false,
|
||||
// (e.g. SELECT foo FROM (SELECT bar as foo FROM t1) sub WHERE sub.foo > 10)
|
||||
// we jump to the Goto instruction below to move on to the next row from the subquery.
|
||||
let jump_label = metadata
|
||||
.next_row_labels
|
||||
.get(id)
|
||||
.expect("subquery has no next row label");
|
||||
|
||||
// These are predicates evaluated outside of the subquery,
|
||||
// so they are translated here.
|
||||
// E.g. SELECT foo FROM (SELECT bar as foo FROM t1) sub WHERE sub.foo > 10
|
||||
@@ -811,7 +780,7 @@ fn open_loop(
|
||||
let condition_metadata = ConditionMetadata {
|
||||
jump_if_condition_is_true: false,
|
||||
jump_target_when_true,
|
||||
jump_target_when_false: *jump_label,
|
||||
jump_target_when_false: loop_labels.next,
|
||||
};
|
||||
translate_condition_expr(
|
||||
program,
|
||||
@@ -837,10 +806,12 @@ fn open_loop(
|
||||
} => {
|
||||
open_loop(program, left, referenced_tables, metadata, syms)?;
|
||||
|
||||
let mut jump_target_when_false = *metadata
|
||||
.next_row_labels
|
||||
let loop_labels = metadata
|
||||
.loop_labels
|
||||
.get(&right.id())
|
||||
.expect("right side of join has no next row label");
|
||||
.expect("right side of join has no loop labels");
|
||||
|
||||
let mut jump_target_when_false = loop_labels.next;
|
||||
|
||||
if *outer {
|
||||
let lj_meta = metadata.left_joins.get(id).unwrap();
|
||||
@@ -850,9 +821,6 @@ fn open_loop(
|
||||
});
|
||||
jump_target_when_false = lj_meta.check_match_flag_label;
|
||||
}
|
||||
metadata
|
||||
.next_row_labels
|
||||
.insert(right.id(), jump_target_when_false);
|
||||
|
||||
open_loop(program, right, referenced_tables, metadata, syms)?;
|
||||
|
||||
@@ -905,10 +873,10 @@ fn open_loop(
|
||||
} else {
|
||||
program.emit_insn(Insn::RewindAsync { cursor_id });
|
||||
}
|
||||
let scan_loop_body_label = program.allocate_label();
|
||||
|
||||
// If the table this cursor is scanning is entirely empty, we exit this loop entirely.
|
||||
let end_of_loop_label = metadata.termination_label_stack.last().unwrap();
|
||||
let loop_labels = metadata
|
||||
.loop_labels
|
||||
.get(id)
|
||||
.expect("scan has no loop labels");
|
||||
program.emit_insn_with_label_dependency(
|
||||
if iter_dir
|
||||
.as_ref()
|
||||
@@ -916,30 +884,25 @@ fn open_loop(
|
||||
{
|
||||
Insn::LastAwait {
|
||||
cursor_id,
|
||||
pc_if_empty: *end_of_loop_label,
|
||||
pc_if_empty: loop_labels.loop_end,
|
||||
}
|
||||
} else {
|
||||
Insn::RewindAwait {
|
||||
cursor_id,
|
||||
pc_if_empty: *end_of_loop_label,
|
||||
pc_if_empty: loop_labels.loop_end,
|
||||
}
|
||||
},
|
||||
*end_of_loop_label,
|
||||
loop_labels.loop_end,
|
||||
);
|
||||
metadata.scan_loop_body_labels.push(scan_loop_body_label);
|
||||
program.defer_label_resolution(scan_loop_body_label, program.offset() as usize);
|
||||
program.defer_label_resolution(loop_labels.loop_start, program.offset() as usize);
|
||||
|
||||
let jump_label = metadata
|
||||
.next_row_labels
|
||||
.get(id)
|
||||
.expect("scan has no next row label");
|
||||
if let Some(preds) = predicates {
|
||||
for expr in preds {
|
||||
let jump_target_when_true = program.allocate_label();
|
||||
let condition_metadata = ConditionMetadata {
|
||||
jump_if_condition_is_true: false,
|
||||
jump_target_when_true,
|
||||
jump_target_when_false: *jump_label,
|
||||
jump_target_when_false: loop_labels.next,
|
||||
};
|
||||
translate_condition_expr(
|
||||
program,
|
||||
@@ -963,6 +926,10 @@ fn open_loop(
|
||||
..
|
||||
} => {
|
||||
let table_cursor_id = program.resolve_cursor_id(&table_reference.table_identifier);
|
||||
let loop_labels = metadata
|
||||
.loop_labels
|
||||
.get(id)
|
||||
.expect("search has no loop labels");
|
||||
// Open the loop for the index search.
|
||||
// Rowid equality point lookups are handled with a SeekRowid instruction which does not loop, since it is a single row lookup.
|
||||
if !matches!(search, Search::RowidEq { .. }) {
|
||||
@@ -971,8 +938,6 @@ fn open_loop(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let scan_loop_body_label = program.allocate_label();
|
||||
metadata.scan_loop_body_labels.push(scan_loop_body_label);
|
||||
let cmp_reg = program.alloc_register();
|
||||
let (cmp_expr, cmp_op) = match search {
|
||||
Search::IndexSearch {
|
||||
@@ -1004,7 +969,7 @@ fn open_loop(
|
||||
_ => unreachable!(),
|
||||
}
|
||||
// If we try to seek to a key that is not present in the table/index, we exit the loop entirely.
|
||||
let end_of_loop_label = *metadata.termination_label_stack.last().unwrap();
|
||||
let end_of_loop_label = loop_labels.loop_end;
|
||||
program.emit_insn_with_label_dependency(
|
||||
match cmp_op {
|
||||
ast::Operator::Equals | ast::Operator::GreaterEquals => Insn::SeekGE {
|
||||
@@ -1038,7 +1003,7 @@ fn open_loop(
|
||||
)?;
|
||||
}
|
||||
|
||||
program.defer_label_resolution(scan_loop_body_label, program.offset() as usize);
|
||||
program.defer_label_resolution(loop_labels.loop_start, program.offset() as usize);
|
||||
// TODO: We are currently only handling ascending indexes.
|
||||
// For conditions like index_key > 10, we have already seeked to the first key greater than 10, and can just scan forward.
|
||||
// For conditions like index_key < 10, we are at the beginning of the index, and will scan forward and emit IdxGE(10) with a conditional jump to the end.
|
||||
@@ -1049,10 +1014,7 @@ fn open_loop(
|
||||
//
|
||||
// For primary key searches we emit RowId and then compare it to the seek value.
|
||||
|
||||
let abort_jump_target = *metadata
|
||||
.next_row_labels
|
||||
.get(id)
|
||||
.expect("search operator has no next row label");
|
||||
let abort_jump_target = loop_labels.next;
|
||||
match cmp_op {
|
||||
ast::Operator::Equals | ast::Operator::LessEquals => {
|
||||
if let Some(index_cursor_id) = index_cursor_id {
|
||||
@@ -1119,8 +1081,6 @@ fn open_loop(
|
||||
}
|
||||
}
|
||||
|
||||
let jump_label = metadata.next_row_labels.get(id).unwrap();
|
||||
|
||||
if let Search::RowidEq { cmp_expr } = search {
|
||||
let src_reg = program.alloc_register();
|
||||
translate_expr(
|
||||
@@ -1135,9 +1095,9 @@ fn open_loop(
|
||||
Insn::SeekRowid {
|
||||
cursor_id: table_cursor_id,
|
||||
src_reg,
|
||||
target_pc: *jump_label,
|
||||
target_pc: loop_labels.next,
|
||||
},
|
||||
*jump_label,
|
||||
loop_labels.next,
|
||||
);
|
||||
}
|
||||
if let Some(predicates) = predicates {
|
||||
@@ -1146,7 +1106,7 @@ fn open_loop(
|
||||
let condition_metadata = ConditionMetadata {
|
||||
jump_if_condition_is_true: false,
|
||||
jump_target_when_true,
|
||||
jump_target_when_false: *jump_label,
|
||||
jump_target_when_false: loop_labels.next,
|
||||
};
|
||||
translate_condition_expr(
|
||||
program,
|
||||
@@ -1162,7 +1122,7 @@ fn open_loop(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Nothing => Ok(()),
|
||||
SourceOperator::Nothing { .. } => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1383,24 +1343,21 @@ fn close_loop(
|
||||
source: &SourceOperator,
|
||||
metadata: &mut Metadata,
|
||||
) -> Result<()> {
|
||||
let loop_labels = *metadata
|
||||
.loop_labels
|
||||
.get(&source.id())
|
||||
.expect("source has no loop labels");
|
||||
match source {
|
||||
SourceOperator::Subquery { id, .. } => {
|
||||
program.resolve_label(
|
||||
*metadata
|
||||
.next_row_labels
|
||||
.get(id)
|
||||
.expect("subquery has no next row label"),
|
||||
program.offset(),
|
||||
);
|
||||
let jump_label = metadata.scan_loop_body_labels.pop().unwrap();
|
||||
SourceOperator::Subquery { .. } => {
|
||||
program.resolve_label(loop_labels.next, program.offset());
|
||||
// A subquery has no cursor to call NextAsync on, so it just emits a Goto
|
||||
// to the Yield instruction, which in turn jumps back to the main loop of the subquery,
|
||||
// so that the next row from the subquery can be read.
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Goto {
|
||||
target_pc: jump_label,
|
||||
target_pc: loop_labels.loop_start,
|
||||
},
|
||||
jump_label,
|
||||
loop_labels.loop_start,
|
||||
);
|
||||
}
|
||||
SourceOperator::Join {
|
||||
@@ -1466,7 +1423,7 @@ fn close_loop(
|
||||
..
|
||||
} => {
|
||||
let cursor_id = program.resolve_cursor_id(&table_reference.table_identifier);
|
||||
program.resolve_label(*metadata.next_row_labels.get(id).unwrap(), program.offset());
|
||||
program.resolve_label(loop_labels.next, program.offset());
|
||||
if iter_dir
|
||||
.as_ref()
|
||||
.is_some_and(|dir| *dir == IterationDirection::Backwards)
|
||||
@@ -1475,8 +1432,6 @@ fn close_loop(
|
||||
} else {
|
||||
program.emit_insn(Insn::NextAsync { cursor_id });
|
||||
}
|
||||
let jump_label = metadata.scan_loop_body_labels.pop().unwrap();
|
||||
|
||||
if iter_dir
|
||||
.as_ref()
|
||||
.is_some_and(|dir| *dir == IterationDirection::Backwards)
|
||||
@@ -1484,17 +1439,17 @@ fn close_loop(
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::PrevAwait {
|
||||
cursor_id,
|
||||
pc_if_next: jump_label,
|
||||
pc_if_next: loop_labels.loop_start,
|
||||
},
|
||||
jump_label,
|
||||
loop_labels.loop_start,
|
||||
);
|
||||
} else {
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::NextAwait {
|
||||
cursor_id,
|
||||
pc_if_next: jump_label,
|
||||
pc_if_next: loop_labels.loop_start,
|
||||
},
|
||||
jump_label,
|
||||
loop_labels.loop_start,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1504,7 +1459,7 @@ fn close_loop(
|
||||
search,
|
||||
..
|
||||
} => {
|
||||
program.resolve_label(*metadata.next_row_labels.get(id).unwrap(), program.offset());
|
||||
program.resolve_label(loop_labels.next, program.offset());
|
||||
if matches!(search, Search::RowidEq { .. }) {
|
||||
// Rowid equality point lookups are handled with a SeekRowid instruction which does not loop, so there is no need to emit a NextAsync instruction.
|
||||
return Ok(());
|
||||
@@ -1518,20 +1473,18 @@ fn close_loop(
|
||||
};
|
||||
|
||||
program.emit_insn(Insn::NextAsync { cursor_id });
|
||||
let jump_label = metadata.scan_loop_body_labels.pop().unwrap();
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::NextAwait {
|
||||
cursor_id,
|
||||
pc_if_next: jump_label,
|
||||
pc_if_next: loop_labels.loop_start,
|
||||
},
|
||||
jump_label,
|
||||
loop_labels.loop_start,
|
||||
);
|
||||
}
|
||||
SourceOperator::Nothing => {}
|
||||
SourceOperator::Nothing { .. } => {}
|
||||
};
|
||||
|
||||
let end_of_loop_label = metadata.termination_label_stack.pop().unwrap();
|
||||
program.resolve_label(end_of_loop_label, program.offset());
|
||||
program.resolve_label(loop_labels.loop_end, program.offset());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1603,6 +1556,8 @@ fn group_by_emit(
|
||||
) -> Result<()> {
|
||||
let sort_loop_start_label = program.allocate_label();
|
||||
let grouping_done_label = program.allocate_label();
|
||||
let group_by_output_row_label = program.allocate_label();
|
||||
let group_by_end_label = program.allocate_label();
|
||||
let group_by_metadata = metadata.group_by_metadata.as_mut().unwrap();
|
||||
|
||||
let GroupByMetadata {
|
||||
@@ -1717,17 +1672,6 @@ fn group_by_emit(
|
||||
subroutine_accumulator_output_label,
|
||||
);
|
||||
|
||||
let group_by_end_idx = {
|
||||
assert!(metadata.termination_label_stack.len() >= 2);
|
||||
// The reason we take the 2nd-to-last label on the stack is because the top of the stack jumps to
|
||||
// the group by output row subroutine (i.e. emit row for a single group), whereas
|
||||
// the 2nd-to-last label on the stack jumps to the end of the entire group by routine.
|
||||
metadata.termination_label_stack.len() - 2
|
||||
};
|
||||
let group_by_end_label = *metadata
|
||||
.termination_label_stack
|
||||
.get(group_by_end_idx)
|
||||
.unwrap();
|
||||
program.add_comment(program.offset(), "check abort flag");
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::IfPos {
|
||||
@@ -1837,14 +1781,13 @@ fn group_by_emit(
|
||||
);
|
||||
|
||||
program.add_comment(program.offset(), "output group by row subroutine start");
|
||||
let output_group_by_row_label = metadata.termination_label_stack.pop().unwrap();
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::IfPos {
|
||||
reg: group_by_metadata.data_in_accumulator_indicator_register,
|
||||
target_pc: output_group_by_row_label,
|
||||
target_pc: group_by_output_row_label,
|
||||
decrement_by: 0,
|
||||
},
|
||||
output_group_by_row_label,
|
||||
group_by_output_row_label,
|
||||
);
|
||||
let group_by_end_without_emitting_row_label = program.allocate_label();
|
||||
program.defer_label_resolution(
|
||||
@@ -1857,7 +1800,7 @@ fn group_by_emit(
|
||||
|
||||
let agg_start_reg = metadata.aggregation_start_register.unwrap();
|
||||
// Resolve the label for the start of the group by output row subroutine
|
||||
program.resolve_label(output_group_by_row_label, program.offset());
|
||||
program.resolve_label(group_by_output_row_label, program.offset());
|
||||
for (i, agg) in aggregates.iter().enumerate() {
|
||||
let agg_result_reg = agg_start_reg + i;
|
||||
program.emit_insn(Insn::AggFinal {
|
||||
@@ -1904,13 +1847,7 @@ fn group_by_emit(
|
||||
result_columns,
|
||||
metadata.result_column_start_register.unwrap(),
|
||||
Some(&precomputed_exprs_to_register),
|
||||
limit.map(|l| {
|
||||
(
|
||||
l,
|
||||
metadata.limit_reg.unwrap(),
|
||||
*metadata.termination_label_stack.last().unwrap(),
|
||||
)
|
||||
}),
|
||||
limit.map(|l| (l, metadata.limit_reg.unwrap(), group_by_end_label)),
|
||||
syms,
|
||||
query_type,
|
||||
)?;
|
||||
@@ -1952,11 +1889,6 @@ fn group_by_emit(
|
||||
return_reg: group_by_metadata.subroutine_accumulator_clear_return_offset_register,
|
||||
});
|
||||
|
||||
assert!(
|
||||
metadata.termination_label_stack.len() >= 2,
|
||||
"termination_label_stack should have at least 2 elements"
|
||||
);
|
||||
let group_by_end_label = metadata.termination_label_stack.pop().unwrap();
|
||||
program.resolve_label(group_by_end_label, program.offset());
|
||||
|
||||
Ok(())
|
||||
@@ -1974,14 +1906,6 @@ fn agg_without_group_by_emit(
|
||||
syms: &SymbolTable,
|
||||
query_type: &SelectQueryType,
|
||||
) -> Result<()> {
|
||||
// Resolve the label for the start of the aggregation phase
|
||||
program.resolve_label(
|
||||
metadata
|
||||
.termination_label_stack
|
||||
.pop()
|
||||
.expect("termination_label_stack should not be empty"),
|
||||
program.offset(),
|
||||
);
|
||||
let agg_start_reg = metadata.aggregation_start_register.unwrap();
|
||||
for (i, agg) in aggregates.iter().enumerate() {
|
||||
let agg_result_reg = agg_start_reg + i;
|
||||
@@ -2027,10 +1951,6 @@ fn order_by_emit(
|
||||
) -> Result<()> {
|
||||
let sort_loop_start_label = program.allocate_label();
|
||||
let sort_loop_end_label = program.allocate_label();
|
||||
program.resolve_label(
|
||||
metadata.termination_label_stack.pop().unwrap(),
|
||||
program.offset(),
|
||||
);
|
||||
let mut pseudo_columns = vec![];
|
||||
for (i, _) in order_by.iter().enumerate() {
|
||||
pseudo_columns.push(Column {
|
||||
|
||||
@@ -211,7 +211,7 @@ fn use_indexes(
|
||||
use_indexes(right, referenced_tables, available_indexes)?;
|
||||
Ok(())
|
||||
}
|
||||
SourceOperator::Nothing => Ok(()),
|
||||
SourceOperator::Nothing { .. } => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ fn eliminate_constants(
|
||||
|
||||
Ok(ConstantConditionEliminationResult::Continue)
|
||||
}
|
||||
SourceOperator::Nothing => Ok(ConstantConditionEliminationResult::Continue),
|
||||
SourceOperator::Nothing { .. } => Ok(ConstantConditionEliminationResult::Continue),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +430,7 @@ fn push_predicates(
|
||||
// Base cases - nowhere else to push to
|
||||
SourceOperator::Scan { .. } => Ok(()),
|
||||
SourceOperator::Search { .. } => Ok(()),
|
||||
SourceOperator::Nothing => Ok(()),
|
||||
SourceOperator::Nothing { .. } => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +585,7 @@ fn push_predicate(
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
SourceOperator::Nothing => Ok(Some(predicate)),
|
||||
SourceOperator::Nothing { .. } => Ok(Some(predicate)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,9 +1018,3 @@ impl TakeOwnership for ast::Expr {
|
||||
std::mem::replace(self, ast::Expr::Literal(ast::Literal::Null))
|
||||
}
|
||||
}
|
||||
|
||||
impl TakeOwnership for SourceOperator {
|
||||
fn take_ownership(&mut self) -> Self {
|
||||
std::mem::replace(self, Self::Nothing)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ impl SourceOperator {
|
||||
.enumerate()
|
||||
.map(|(i, col)| (table_reference.table_index, col, i))
|
||||
.collect(),
|
||||
SourceOperator::Nothing => Vec::new(),
|
||||
SourceOperator::Nothing { .. } => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +222,9 @@ pub enum SourceOperator {
|
||||
// Nothing operator
|
||||
// This operator is used to represent an empty query.
|
||||
// e.g. SELECT * from foo WHERE 0 will eventually be optimized to Nothing.
|
||||
Nothing,
|
||||
Nothing {
|
||||
id: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// The type of the table reference, either BTreeTable or Subquery
|
||||
@@ -306,7 +308,7 @@ impl SourceOperator {
|
||||
SourceOperator::Scan { id, .. } => *id,
|
||||
SourceOperator::Search { id, .. } => *id,
|
||||
SourceOperator::Subquery { id, .. } => *id,
|
||||
SourceOperator::Nothing => unreachable!(),
|
||||
SourceOperator::Nothing { id } => *id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,7 +447,7 @@ impl Display for SourceOperator {
|
||||
SourceOperator::Subquery { plan, .. } => {
|
||||
fmt_operator(&plan.source, f, level + 1, last)
|
||||
}
|
||||
SourceOperator::Nothing => Ok(()),
|
||||
SourceOperator::Nothing { .. } => Ok(()),
|
||||
}
|
||||
}
|
||||
writeln!(f, "QUERY PLAN")?;
|
||||
@@ -490,7 +492,7 @@ pub fn get_table_ref_bitmask_for_operator<'a>(
|
||||
.unwrap();
|
||||
}
|
||||
SourceOperator::Subquery { .. } => {}
|
||||
SourceOperator::Nothing => {}
|
||||
SourceOperator::Nothing { .. } => {}
|
||||
}
|
||||
Ok(table_refs_mask)
|
||||
}
|
||||
|
||||
@@ -324,7 +324,12 @@ pub fn parse_from(
|
||||
operator_id_counter: &mut OperatorIdCounter,
|
||||
) -> Result<(SourceOperator, Vec<TableReference>)> {
|
||||
if from.as_ref().and_then(|f| f.select.as_ref()).is_none() {
|
||||
return Ok((SourceOperator::Nothing, vec![]));
|
||||
return Ok((
|
||||
SourceOperator::Nothing {
|
||||
id: operator_id_counter.get_next_id(),
|
||||
},
|
||||
vec![],
|
||||
));
|
||||
}
|
||||
|
||||
let mut table_index = 0;
|
||||
|
||||
Reference in New Issue
Block a user