mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-07 17:24:24 +01:00
Merge 'Small perf optimizations to statement preparation' from Jussi Saurio
```bash
Prepare `SELECT 1`/Limbo/SELECT 1
time: [765.94 ns 768.26 ns 771.03 ns]
change: [-7.8340% -7.4887% -7.1406%] (p = 0.00 < 0.05)
Performance has improved.
Prepare `SELECT * FROM users LIMIT 1`/Limbo/SELECT * FROM users LIMIT 1
time: [1.5673 µs 1.5699 µs 1.5731 µs]
change: [-10.810% -9.7122% -8.4951%] (p = 0.00 < 0.05)
Performance has improved.
Prepare `SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY cou...
time: [4.1331 µs 4.1421 µs 4.1513 µs]
change: [-9.3157% -9.0255% -8.7372%] (p = 0.00 < 0.05)
Performance has improved.
```
flamegraph for prepare `SELECT 1`:
<img width="1718" alt="Screenshot 2025-02-03 at 10 34 14"
src="https://github.com/user-
attachments/assets/ba67fe2f-78b2-4796-9a09-837d8e79fe62" />
Closes #872
This commit is contained in:
@@ -338,7 +338,7 @@ impl Connection {
|
||||
*select,
|
||||
&self.db.syms.borrow(),
|
||||
)?;
|
||||
optimize_plan(&mut plan)?;
|
||||
optimize_plan(&mut plan, &self.schema.borrow())?;
|
||||
println!("{}", plan);
|
||||
}
|
||||
_ => todo!(),
|
||||
|
||||
@@ -66,8 +66,14 @@ impl Table {
|
||||
|
||||
pub fn get_column_at(&self, index: usize) -> &Column {
|
||||
match self {
|
||||
Self::BTree(table) => table.columns.get(index).unwrap(),
|
||||
Self::Pseudo(table) => table.columns.get(index).unwrap(),
|
||||
Self::BTree(table) => table
|
||||
.columns
|
||||
.get(index)
|
||||
.expect("column index out of bounds"),
|
||||
Self::Pseudo(table) => table
|
||||
.columns
|
||||
.get(index)
|
||||
.expect("column index out of bounds"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ pub fn translate_delete(
|
||||
syms: &SymbolTable,
|
||||
) -> Result<()> {
|
||||
let mut delete_plan = prepare_delete_plan(schema, tbl_name, where_clause, limit)?;
|
||||
optimize_plan(&mut delete_plan)?;
|
||||
optimize_plan(&mut delete_plan, schema)?;
|
||||
emit_program(program, delete_plan, syms)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ pub fn prepare_delete_plan(
|
||||
order_by: None,
|
||||
limit: resolved_limit,
|
||||
offset: resolved_offset,
|
||||
available_indexes: vec![],
|
||||
contains_constant_false_condition: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// This module contains code for emitting bytecode instructions for SQL query execution.
|
||||
// It handles translating high-level SQL operations into low-level bytecode that can be executed by the virtual machine.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use sqlite3_parser::ast::{self};
|
||||
|
||||
use crate::function::Func;
|
||||
@@ -76,11 +74,12 @@ pub struct TranslateCtx<'a> {
|
||||
pub meta_group_by: Option<GroupByMetadata>,
|
||||
// metadata for the order by operator
|
||||
pub meta_sort: Option<SortMetadata>,
|
||||
// mapping between Join operator id and associated metadata (for left joins only)
|
||||
pub meta_left_joins: HashMap<usize, LeftJoinMetadata>,
|
||||
/// mapping between table loop index and associated metadata (for left joins only)
|
||||
/// this metadata exists for the right table in a given left join
|
||||
pub meta_left_joins: Vec<Option<LeftJoinMetadata>>,
|
||||
// We need to emit result columns in the order they are present in the SELECT, but they may not be in the same order in the ORDER BY sorter.
|
||||
// This vector holds the indexes of the result columns in the ORDER BY sorter.
|
||||
pub result_column_indexes_in_orderby_sorter: HashMap<usize, usize>,
|
||||
pub result_column_indexes_in_orderby_sorter: Vec<usize>,
|
||||
// We might skip adding a SELECT result column into the ORDER BY sorter if it is an exact match in the ORDER BY keys.
|
||||
// This vector holds the indexes of the result columns that we need to skip.
|
||||
pub result_columns_to_skip_in_orderby_sorter: Option<Vec<usize>>,
|
||||
@@ -101,6 +100,8 @@ pub enum OperationMode {
|
||||
fn prologue<'a>(
|
||||
program: &mut ProgramBuilder,
|
||||
syms: &'a SymbolTable,
|
||||
table_count: usize,
|
||||
result_column_count: usize,
|
||||
) -> Result<(TranslateCtx<'a>, BranchOffset, BranchOffset)> {
|
||||
let init_label = program.allocate_label();
|
||||
|
||||
@@ -111,7 +112,7 @@ fn prologue<'a>(
|
||||
let start_offset = program.offset();
|
||||
|
||||
let t_ctx = TranslateCtx {
|
||||
labels_main_loop: Vec::new(),
|
||||
labels_main_loop: (0..table_count).map(|_| LoopLabels::new(program)).collect(),
|
||||
label_main_loop_end: None,
|
||||
reg_agg_start: None,
|
||||
reg_limit: None,
|
||||
@@ -119,9 +120,9 @@ fn prologue<'a>(
|
||||
reg_limit_offset_sum: None,
|
||||
reg_result_cols_start: None,
|
||||
meta_group_by: None,
|
||||
meta_left_joins: HashMap::new(),
|
||||
meta_left_joins: (0..table_count).map(|_| None).collect(),
|
||||
meta_sort: None,
|
||||
result_column_indexes_in_orderby_sorter: HashMap::new(),
|
||||
result_column_indexes_in_orderby_sorter: (0..result_column_count).collect(),
|
||||
result_columns_to_skip_in_orderby_sorter: None,
|
||||
resolver: Resolver::new(syms),
|
||||
};
|
||||
@@ -167,7 +168,12 @@ fn emit_program_for_select(
|
||||
mut plan: SelectPlan,
|
||||
syms: &SymbolTable,
|
||||
) -> Result<()> {
|
||||
let (mut t_ctx, init_label, start_offset) = prologue(program, syms)?;
|
||||
let (mut t_ctx, init_label, start_offset) = prologue(
|
||||
program,
|
||||
syms,
|
||||
plan.table_references.len(),
|
||||
plan.result_columns.len(),
|
||||
)?;
|
||||
|
||||
// Trivial exit on LIMIT 0
|
||||
if let Some(limit) = plan.limit {
|
||||
@@ -274,7 +280,12 @@ fn emit_program_for_delete(
|
||||
mut plan: DeletePlan,
|
||||
syms: &SymbolTable,
|
||||
) -> Result<()> {
|
||||
let (mut t_ctx, init_label, start_offset) = prologue(program, syms)?;
|
||||
let (mut t_ctx, init_label, start_offset) = prologue(
|
||||
program,
|
||||
syms,
|
||||
plan.table_references.len(),
|
||||
plan.result_columns.len(),
|
||||
)?;
|
||||
|
||||
// No rows will be read from source table loops if there is a constant false condition eg. WHERE 0
|
||||
let after_main_loop_label = program.allocate_label();
|
||||
|
||||
@@ -43,6 +43,16 @@ pub struct LoopLabels {
|
||||
loop_end: BranchOffset,
|
||||
}
|
||||
|
||||
impl LoopLabels {
|
||||
pub fn new(program: &mut ProgramBuilder) -> Self {
|
||||
Self {
|
||||
loop_start: program.allocate_label(),
|
||||
next: program.allocate_label(),
|
||||
loop_end: program.allocate_label(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize resources needed for the source operators (tables, joins, etc)
|
||||
pub fn init_loop(
|
||||
program: &mut ProgramBuilder,
|
||||
@@ -50,14 +60,11 @@ pub fn init_loop(
|
||||
tables: &[TableReference],
|
||||
mode: &OperationMode,
|
||||
) -> Result<()> {
|
||||
assert!(
|
||||
t_ctx.meta_left_joins.len() == tables.len(),
|
||||
"meta_left_joins length does not match tables length"
|
||||
);
|
||||
for (table_index, table) in tables.iter().enumerate() {
|
||||
let loop_labels = LoopLabels {
|
||||
next: program.allocate_label(),
|
||||
loop_start: program.allocate_label(),
|
||||
loop_end: program.allocate_label(),
|
||||
};
|
||||
t_ctx.labels_main_loop.push(loop_labels);
|
||||
|
||||
// Initialize bookkeeping for OUTER JOIN
|
||||
if let Some(join_info) = table.join_info.as_ref() {
|
||||
if join_info.outer {
|
||||
@@ -66,7 +73,7 @@ pub fn init_loop(
|
||||
label_match_flag_set_true: program.allocate_label(),
|
||||
label_match_flag_check_value: program.allocate_label(),
|
||||
};
|
||||
t_ctx.meta_left_joins.insert(table_index, lj_metadata);
|
||||
t_ctx.meta_left_joins[table_index] = Some(lj_metadata);
|
||||
}
|
||||
}
|
||||
match &table.op {
|
||||
@@ -181,7 +188,7 @@ pub fn open_loop(
|
||||
// This is used to determine whether to emit actual columns or NULLs for the columns of the right table.
|
||||
if let Some(join_info) = table.join_info.as_ref() {
|
||||
if join_info.outer {
|
||||
let lj_meta = t_ctx.meta_left_joins.get(&table_index).unwrap();
|
||||
let lj_meta = t_ctx.meta_left_joins[table_index].as_ref().unwrap();
|
||||
program.emit_insn(Insn::Integer {
|
||||
value: 0,
|
||||
dest: lj_meta.reg_match_flag,
|
||||
@@ -465,7 +472,7 @@ pub fn open_loop(
|
||||
// for the right table's cursor.
|
||||
if let Some(join_info) = table.join_info.as_ref() {
|
||||
if join_info.outer {
|
||||
let lj_meta = t_ctx.meta_left_joins.get(&table_index).unwrap();
|
||||
let lj_meta = t_ctx.meta_left_joins[table_index].as_ref().unwrap();
|
||||
program.resolve_label(lj_meta.label_match_flag_set_true, program.offset());
|
||||
program.emit_insn(Insn::Integer {
|
||||
value: 1,
|
||||
@@ -731,7 +738,7 @@ pub fn close_loop(
|
||||
// and emit a row with NULLs for the right table, and then jump back to the next row of the left table.
|
||||
if let Some(join_info) = table.join_info.as_ref() {
|
||||
if join_info.outer {
|
||||
let lj_meta = t_ctx.meta_left_joins.get(&table_index).unwrap();
|
||||
let lj_meta = t_ctx.meta_left_joins[table_index].as_ref().unwrap();
|
||||
// The left join match flag is set to 1 when there is any match on the right table
|
||||
// (e.g. SELECT * FROM t1 LEFT JOIN t2 ON t1.a = t2.a).
|
||||
// If the left join match flag has been set to 1, we jump to the next row on the outer table,
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
use std::rc::Rc;
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
|
||||
use sqlite3_parser::ast;
|
||||
|
||||
use crate::{schema::Index, Result};
|
||||
use crate::{
|
||||
schema::{Index, Schema},
|
||||
Result,
|
||||
};
|
||||
|
||||
use super::plan::{
|
||||
DeletePlan, Direction, IterationDirection, Operation, Plan, Search, SelectPlan, TableReference,
|
||||
WhereTerm,
|
||||
};
|
||||
|
||||
pub fn optimize_plan(plan: &mut Plan) -> Result<()> {
|
||||
pub fn optimize_plan(plan: &mut Plan, schema: &Schema) -> Result<()> {
|
||||
match plan {
|
||||
Plan::Select(plan) => optimize_select_plan(plan),
|
||||
Plan::Delete(plan) => optimize_delete_plan(plan),
|
||||
Plan::Select(plan) => optimize_select_plan(plan, schema),
|
||||
Plan::Delete(plan) => optimize_delete_plan(plan, schema),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +24,8 @@ pub fn optimize_plan(plan: &mut Plan) -> Result<()> {
|
||||
* TODO: these could probably be done in less passes,
|
||||
* but having them separate makes them easier to understand
|
||||
*/
|
||||
fn optimize_select_plan(plan: &mut SelectPlan) -> Result<()> {
|
||||
optimize_subqueries(plan)?;
|
||||
fn optimize_select_plan(plan: &mut SelectPlan, schema: &Schema) -> Result<()> {
|
||||
optimize_subqueries(plan, schema)?;
|
||||
rewrite_exprs_select(plan)?;
|
||||
if let ConstantConditionEliminationResult::ImpossibleCondition =
|
||||
eliminate_constant_conditions(&mut plan.where_clause)?
|
||||
@@ -33,16 +36,16 @@ fn optimize_select_plan(plan: &mut SelectPlan) -> Result<()> {
|
||||
|
||||
use_indexes(
|
||||
&mut plan.table_references,
|
||||
&plan.available_indexes,
|
||||
&schema.indexes,
|
||||
&mut plan.where_clause,
|
||||
)?;
|
||||
|
||||
eliminate_unnecessary_orderby(plan)?;
|
||||
eliminate_unnecessary_orderby(plan, schema)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn optimize_delete_plan(plan: &mut DeletePlan) -> Result<()> {
|
||||
fn optimize_delete_plan(plan: &mut DeletePlan, schema: &Schema) -> Result<()> {
|
||||
rewrite_exprs_delete(plan)?;
|
||||
if let ConstantConditionEliminationResult::ImpossibleCondition =
|
||||
eliminate_constant_conditions(&mut plan.where_clause)?
|
||||
@@ -53,17 +56,17 @@ fn optimize_delete_plan(plan: &mut DeletePlan) -> Result<()> {
|
||||
|
||||
use_indexes(
|
||||
&mut plan.table_references,
|
||||
&plan.available_indexes,
|
||||
&schema.indexes,
|
||||
&mut plan.where_clause,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn optimize_subqueries(plan: &mut SelectPlan) -> Result<()> {
|
||||
fn optimize_subqueries(plan: &mut SelectPlan, schema: &Schema) -> Result<()> {
|
||||
for table in plan.table_references.iter_mut() {
|
||||
if let Operation::Subquery { plan, .. } = &mut table.op {
|
||||
optimize_select_plan(&mut *plan)?;
|
||||
optimize_select_plan(&mut *plan, schema)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +76,7 @@ fn optimize_subqueries(plan: &mut SelectPlan) -> Result<()> {
|
||||
fn query_is_already_ordered_by(
|
||||
table_references: &[TableReference],
|
||||
key: &mut ast::Expr,
|
||||
available_indexes: &Vec<Rc<Index>>,
|
||||
available_indexes: &HashMap<String, Vec<Rc<Index>>>,
|
||||
) -> Result<bool> {
|
||||
let first_table = table_references.first();
|
||||
if first_table.is_none() {
|
||||
@@ -86,10 +89,9 @@ fn query_is_already_ordered_by(
|
||||
Search::RowidEq { .. } => Ok(key.is_rowid_alias_of(0)),
|
||||
Search::RowidSearch { .. } => Ok(key.is_rowid_alias_of(0)),
|
||||
Search::IndexSearch { index, .. } => {
|
||||
let index_idx = key.check_index_scan(0, &table_reference, available_indexes)?;
|
||||
let index_is_the_same = index_idx
|
||||
.map(|i| Rc::ptr_eq(&available_indexes[i], index))
|
||||
.unwrap_or(false);
|
||||
let index_rc = key.check_index_scan(0, &table_reference, available_indexes)?;
|
||||
let index_is_the_same =
|
||||
index_rc.map(|irc| Rc::ptr_eq(index, &irc)).unwrap_or(false);
|
||||
Ok(index_is_the_same)
|
||||
}
|
||||
},
|
||||
@@ -97,7 +99,7 @@ fn query_is_already_ordered_by(
|
||||
}
|
||||
}
|
||||
|
||||
fn eliminate_unnecessary_orderby(plan: &mut SelectPlan) -> Result<()> {
|
||||
fn eliminate_unnecessary_orderby(plan: &mut SelectPlan, schema: &Schema) -> Result<()> {
|
||||
if plan.order_by.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -115,7 +117,7 @@ fn eliminate_unnecessary_orderby(plan: &mut SelectPlan) -> Result<()> {
|
||||
let (key, direction) = o.first_mut().unwrap();
|
||||
|
||||
let already_ordered =
|
||||
query_is_already_ordered_by(&plan.table_references, key, &plan.available_indexes)?;
|
||||
query_is_already_ordered_by(&plan.table_references, key, &schema.indexes)?;
|
||||
|
||||
if already_ordered {
|
||||
push_scan_direction(&mut plan.table_references[0], direction);
|
||||
@@ -136,7 +138,7 @@ fn eliminate_unnecessary_orderby(plan: &mut SelectPlan) -> Result<()> {
|
||||
*/
|
||||
fn use_indexes(
|
||||
table_references: &mut [TableReference],
|
||||
available_indexes: &Vec<Rc<Index>>,
|
||||
available_indexes: &HashMap<String, Vec<Rc<Index>>>,
|
||||
where_clause: &mut Vec<WhereTerm>,
|
||||
) -> Result<()> {
|
||||
if where_clause.is_empty() {
|
||||
@@ -274,8 +276,8 @@ pub trait Optimizable {
|
||||
&mut self,
|
||||
table_index: usize,
|
||||
table_reference: &TableReference,
|
||||
available_indexes: &[Rc<Index>],
|
||||
) -> Result<Option<usize>>;
|
||||
available_indexes: &HashMap<String, Vec<Rc<Index>>>,
|
||||
) -> Result<Option<Rc<Index>>>;
|
||||
}
|
||||
|
||||
impl Optimizable for ast::Expr {
|
||||
@@ -293,19 +295,22 @@ impl Optimizable for ast::Expr {
|
||||
&mut self,
|
||||
table_index: usize,
|
||||
table_reference: &TableReference,
|
||||
available_indexes: &[Rc<Index>],
|
||||
) -> Result<Option<usize>> {
|
||||
available_indexes: &HashMap<String, Vec<Rc<Index>>>,
|
||||
) -> Result<Option<Rc<Index>>> {
|
||||
match self {
|
||||
Self::Column { table, column, .. } => {
|
||||
if *table != table_index {
|
||||
return Ok(None);
|
||||
}
|
||||
for (idx, index) in available_indexes.iter().enumerate() {
|
||||
if index.table_name == table_reference.table.get_name() {
|
||||
let column = table_reference.table.get_column_at(*column);
|
||||
if index.columns.first().unwrap().name == column.name {
|
||||
return Ok(Some(idx));
|
||||
}
|
||||
let Some(available_indexes_for_table) =
|
||||
available_indexes.get(table_reference.table.get_name())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let column = table_reference.table.get_column_at(*column);
|
||||
for index in available_indexes_for_table.iter() {
|
||||
if index.columns.first().unwrap().name == column.name {
|
||||
return Ok(Some(index.clone()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
@@ -489,7 +494,7 @@ pub fn try_extract_index_search_expression(
|
||||
cond: &mut WhereTerm,
|
||||
table_index: usize,
|
||||
table_reference: &TableReference,
|
||||
available_indexes: &[Rc<Index>],
|
||||
available_indexes: &HashMap<String, Vec<Rc<Index>>>,
|
||||
) -> Result<Option<Search>> {
|
||||
if cond.eval_at_loop != table_index {
|
||||
return Ok(None);
|
||||
@@ -556,7 +561,7 @@ pub fn try_extract_index_search_expression(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index_index) =
|
||||
if let Some(index_rc) =
|
||||
lhs.check_index_scan(table_index, &table_reference, available_indexes)?
|
||||
{
|
||||
match operator {
|
||||
@@ -567,7 +572,7 @@ pub fn try_extract_index_search_expression(
|
||||
| ast::Operator::LessEquals => {
|
||||
let rhs_owned = rhs.take_ownership();
|
||||
return Ok(Some(Search::IndexSearch {
|
||||
index: available_indexes[index_index].clone(),
|
||||
index: index_rc,
|
||||
cmp_op: *operator,
|
||||
cmp_expr: WhereTerm {
|
||||
expr: rhs_owned,
|
||||
@@ -580,7 +585,7 @@ pub fn try_extract_index_search_expression(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index_index) =
|
||||
if let Some(index_rc) =
|
||||
rhs.check_index_scan(table_index, &table_reference, available_indexes)?
|
||||
{
|
||||
match operator {
|
||||
@@ -591,7 +596,7 @@ pub fn try_extract_index_search_expression(
|
||||
| ast::Operator::LessEquals => {
|
||||
let lhs_owned = lhs.take_ownership();
|
||||
return Ok(Some(Search::IndexSearch {
|
||||
index: available_indexes[index_index].clone(),
|
||||
index: index_rc,
|
||||
cmp_op: opposite_cmp_op(*operator),
|
||||
cmp_expr: WhereTerm {
|
||||
expr: lhs_owned,
|
||||
|
||||
@@ -142,7 +142,7 @@ pub fn emit_order_by(
|
||||
let reg = start_reg + i;
|
||||
program.emit_insn(Insn::Column {
|
||||
cursor_id,
|
||||
column: t_ctx.result_column_indexes_in_orderby_sorter[&i],
|
||||
column: t_ctx.result_column_indexes_in_orderby_sorter[i],
|
||||
dest: reg,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,8 +89,6 @@ pub struct SelectPlan {
|
||||
pub limit: Option<isize>,
|
||||
/// offset clause
|
||||
pub offset: Option<isize>,
|
||||
/// all the indexes available
|
||||
pub available_indexes: Vec<Rc<Index>>,
|
||||
/// query contains a constant condition that is always false
|
||||
pub contains_constant_false_condition: bool,
|
||||
/// query type (top level or subquery)
|
||||
@@ -112,8 +110,6 @@ pub struct DeletePlan {
|
||||
pub limit: Option<isize>,
|
||||
/// offset clause
|
||||
pub offset: Option<isize>,
|
||||
/// all the indexes available
|
||||
pub available_indexes: Vec<Rc<Index>>,
|
||||
/// query contains a constant condition that is always false
|
||||
pub contains_constant_false_condition: bool,
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn translate_select(
|
||||
syms: &SymbolTable,
|
||||
) -> Result<()> {
|
||||
let mut select_plan = prepare_select_plan(schema, select, syms)?;
|
||||
optimize_plan(&mut select_plan)?;
|
||||
optimize_plan(&mut select_plan, schema)?;
|
||||
emit_program(program, select_plan, syms)
|
||||
}
|
||||
|
||||
@@ -48,16 +48,36 @@ pub fn prepare_select_plan(
|
||||
// Parse the FROM clause into a vec of TableReferences. Fold all the join conditions expressions into the WHERE clause.
|
||||
let table_references = parse_from(schema, from, syms, &mut where_predicates)?;
|
||||
|
||||
// Preallocate space for the result columns
|
||||
let result_columns = Vec::with_capacity(
|
||||
columns
|
||||
.iter()
|
||||
.map(|c| match c {
|
||||
// Allocate space for all columns in all tables
|
||||
ResultColumn::Star => {
|
||||
table_references.iter().map(|t| t.columns().len()).sum()
|
||||
}
|
||||
// Guess 5 columns if we can't find the table using the identifier (maybe it's in [brackets] or `tick_quotes`, or miXeDcAse)
|
||||
ResultColumn::TableStar(n) => table_references
|
||||
.iter()
|
||||
.find(|t| t.identifier == n.0)
|
||||
.map(|t| t.columns().len())
|
||||
.unwrap_or(5),
|
||||
// Otherwise allocate space for 1 column
|
||||
ResultColumn::Expr(_, _) => 1,
|
||||
})
|
||||
.sum(),
|
||||
);
|
||||
|
||||
let mut plan = SelectPlan {
|
||||
table_references,
|
||||
result_columns: vec![],
|
||||
result_columns,
|
||||
where_clause: where_predicates,
|
||||
group_by: None,
|
||||
order_by: None,
|
||||
aggregates: vec![],
|
||||
limit: None,
|
||||
offset: None,
|
||||
available_indexes: schema.indexes.clone().into_values().flatten().collect(),
|
||||
contains_constant_false_condition: false,
|
||||
query_type: SelectQueryType::TopLevel,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
vdbe::{builder::ProgramBuilder, insn::Insn},
|
||||
Result,
|
||||
@@ -7,6 +5,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
emitter::{emit_query, Resolver, TranslateCtx},
|
||||
main_loop::LoopLabels,
|
||||
plan::{Operation, SelectPlan, SelectQueryType, TableReference},
|
||||
};
|
||||
|
||||
@@ -68,14 +67,16 @@ pub fn emit_subquery<'a>(
|
||||
}
|
||||
let end_coroutine_label = program.allocate_label();
|
||||
let mut metadata = TranslateCtx {
|
||||
labels_main_loop: vec![],
|
||||
labels_main_loop: (0..plan.table_references.len())
|
||||
.map(|_| LoopLabels::new(program))
|
||||
.collect(),
|
||||
label_main_loop_end: None,
|
||||
meta_group_by: None,
|
||||
meta_left_joins: HashMap::new(),
|
||||
meta_left_joins: (0..plan.table_references.len()).map(|_| None).collect(),
|
||||
meta_sort: None,
|
||||
reg_agg_start: None,
|
||||
reg_result_cols_start: None,
|
||||
result_column_indexes_in_orderby_sorter: HashMap::new(),
|
||||
result_column_indexes_in_orderby_sorter: (0..plan.result_columns.len()).collect(),
|
||||
result_columns_to_skip_in_orderby_sorter: None,
|
||||
reg_limit: plan.limit.map(|_| program.alloc_register()),
|
||||
reg_offset: plan.offset.map(|_| program.alloc_register()),
|
||||
|
||||
@@ -15,7 +15,6 @@ use super::{BranchOffset, CursorID, Insn, InsnReference, Program};
|
||||
#[allow(dead_code)]
|
||||
pub struct ProgramBuilder {
|
||||
next_free_register: usize,
|
||||
next_free_label: i32,
|
||||
next_free_cursor_id: usize,
|
||||
insns: Vec<Insn>,
|
||||
// for temporarily storing instructions that will be put after Transaction opcode
|
||||
@@ -23,8 +22,8 @@ pub struct ProgramBuilder {
|
||||
next_insn_label: Option<BranchOffset>,
|
||||
// Cursors that are referenced by the program. Indexed by CursorID.
|
||||
pub cursor_ref: Vec<(Option<String>, CursorType)>,
|
||||
// Hashmap of label to insn reference. Resolved in build().
|
||||
label_to_resolved_offset: HashMap<i32, u32>,
|
||||
/// A vector where index=label number, value=resolved offset. Resolved in build().
|
||||
label_to_resolved_offset: Vec<Option<InsnReference>>,
|
||||
// Bitmask of cursors that have emitted a SeekRowid instruction.
|
||||
seekrowid_emitted_bitmask: u64,
|
||||
// map of instruction index to manual comment (used in EXPLAIN only)
|
||||
@@ -57,13 +56,12 @@ impl ProgramBuilder {
|
||||
pub fn new(query_mode: QueryMode) -> Self {
|
||||
Self {
|
||||
next_free_register: 1,
|
||||
next_free_label: 0,
|
||||
next_free_cursor_id: 0,
|
||||
insns: Vec::new(),
|
||||
next_insn_label: None,
|
||||
cursor_ref: Vec::new(),
|
||||
constant_insns: Vec::new(),
|
||||
label_to_resolved_offset: HashMap::new(),
|
||||
label_to_resolved_offset: Vec::with_capacity(4), // 4 is arbitrary, we guess to assign at least this much
|
||||
seekrowid_emitted_bitmask: 0,
|
||||
comments: if query_mode == QueryMode::Explain {
|
||||
Some(HashMap::new())
|
||||
@@ -101,8 +99,10 @@ impl ProgramBuilder {
|
||||
|
||||
pub fn emit_insn(&mut self, insn: Insn) {
|
||||
if let Some(label) = self.next_insn_label {
|
||||
self.label_to_resolved_offset
|
||||
.insert(label.to_label_value(), self.insns.len() as InsnReference);
|
||||
self.label_to_resolved_offset.insert(
|
||||
label.to_label_value() as usize,
|
||||
Some(self.insns.len() as InsnReference),
|
||||
);
|
||||
self.next_insn_label = None;
|
||||
}
|
||||
self.insns.push(insn);
|
||||
@@ -195,8 +195,9 @@ impl ProgramBuilder {
|
||||
}
|
||||
|
||||
pub fn allocate_label(&mut self) -> BranchOffset {
|
||||
self.next_free_label -= 1;
|
||||
BranchOffset::Label(self.next_free_label)
|
||||
let label_n = self.label_to_resolved_offset.len();
|
||||
self.label_to_resolved_offset.push(None);
|
||||
BranchOffset::Label(label_n as u32)
|
||||
}
|
||||
|
||||
// Effectively a GOTO <next insn> without the need to emit an explicit GOTO instruction.
|
||||
@@ -209,8 +210,8 @@ impl ProgramBuilder {
|
||||
pub fn resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset) {
|
||||
assert!(matches!(label, BranchOffset::Label(_)));
|
||||
assert!(matches!(to_offset, BranchOffset::Offset(_)));
|
||||
self.label_to_resolved_offset
|
||||
.insert(label.to_label_value(), to_offset.to_offset_int());
|
||||
self.label_to_resolved_offset[label.to_label_value() as usize] =
|
||||
Some(to_offset.to_offset_int());
|
||||
}
|
||||
|
||||
/// Resolve unresolved labels to a specific offset in the instruction list.
|
||||
@@ -221,10 +222,16 @@ impl ProgramBuilder {
|
||||
pub fn resolve_labels(&mut self) {
|
||||
let resolve = |pc: &mut BranchOffset, insn_name: &str| {
|
||||
if let BranchOffset::Label(label) = pc {
|
||||
let to_offset = *self.label_to_resolved_offset.get(label).unwrap_or_else(|| {
|
||||
panic!("Reference to undefined label in {}: {}", insn_name, label)
|
||||
});
|
||||
*pc = BranchOffset::Offset(to_offset);
|
||||
let to_offset = self
|
||||
.label_to_resolved_offset
|
||||
.get(*label as usize)
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Reference to undefined label in {}: {}", insn_name, label)
|
||||
});
|
||||
*pc = BranchOffset::Offset(
|
||||
to_offset
|
||||
.unwrap_or_else(|| panic!("Unresolved label in {}: {}", insn_name, label)),
|
||||
);
|
||||
}
|
||||
};
|
||||
for insn in self.insns.iter_mut() {
|
||||
|
||||
@@ -77,7 +77,7 @@ pub enum BranchOffset {
|
||||
/// A label is a named location in the program.
|
||||
/// If there are references to it, it must always be resolved to an Offset
|
||||
/// via program.resolve_label().
|
||||
Label(i32),
|
||||
Label(u32),
|
||||
/// An offset is a direct index into the instruction list.
|
||||
Offset(InsnReference),
|
||||
/// A placeholder is a temporary value to satisfy the compiler.
|
||||
@@ -106,7 +106,7 @@ impl BranchOffset {
|
||||
}
|
||||
|
||||
/// Returns the label value. Panics if the branch offset is an offset or placeholder.
|
||||
pub fn to_label_value(&self) -> i32 {
|
||||
pub fn to_label_value(&self) -> u32 {
|
||||
match self {
|
||||
BranchOffset::Label(v) => *v,
|
||||
BranchOffset::Offset(_) => unreachable!("Offset cannot be converted to label value"),
|
||||
@@ -119,7 +119,7 @@ impl BranchOffset {
|
||||
/// label or placeholder.
|
||||
pub fn to_debug_int(&self) -> i32 {
|
||||
match self {
|
||||
BranchOffset::Label(v) => *v,
|
||||
BranchOffset::Label(v) => *v as i32,
|
||||
BranchOffset::Offset(v) => *v as i32,
|
||||
BranchOffset::Placeholder => i32::MAX,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user