diff --git a/core/lib.rs b/core/lib.rs index c753a0ec8..acb59adc0 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -5,7 +5,6 @@ mod io; mod pager; mod pseudo; mod schema; -mod sorter; mod sqlite3_ondisk; mod storage; mod translate; diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 41d70e9f7..9ea943a8f 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -6,7 +6,7 @@ use crate::{ schema::{Schema, Table, Type}, translate::select::{ColumnInfo, Select, SrcTable}, util::normalize_ident, - vdbe::{BranchOffset, Insn, ProgramBuilder}, + vdbe::{BranchOffset, Insn, builder::ProgramBuilder}, }; pub fn build_select<'a>(schema: &Schema, select: &'a ast::Select) -> Result> { diff --git a/core/translate/mod.rs b/core/translate/mod.rs index 074e01ef5..68fa38d04 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -15,7 +15,7 @@ use crate::translate::where_clause::{ }; use crate::types::{OwnedRecord, OwnedValue}; use crate::util::normalize_ident; -use crate::vdbe::{BranchOffset, Insn, Program, ProgramBuilder}; +use crate::vdbe::{BranchOffset, Insn, Program, builder::ProgramBuilder}; use anyhow::Result; use expr::{build_select, maybe_apply_affinity, translate_expr}; use sqlite3_parser::ast::{self, Literal}; diff --git a/core/translate/where_clause.rs b/core/translate/where_clause.rs index 30e29ca25..062d0f124 100644 --- a/core/translate/where_clause.rs +++ b/core/translate/where_clause.rs @@ -5,7 +5,7 @@ use crate::{ translate::expr::{resolve_ident_qualified, resolve_ident_table, translate_expr}, function::SingleRowFunc, translate::select::Select, - vdbe::{BranchOffset, Insn, ProgramBuilder}, + vdbe::{BranchOffset, Insn, builder::ProgramBuilder}, }; const HARDCODED_CURSOR_LEFT_TABLE: usize = 0; diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs new file mode 100644 index 000000000..9305b65da --- /dev/null +++ b/core/vdbe/builder.rs @@ -0,0 +1,295 @@ +use super::{BranchOffset, CursorID, Insn, InsnReference, Program, Table}; + +pub struct ProgramBuilder { + next_free_register: usize, + next_free_label: BranchOffset, + next_free_cursor_id: usize, + insns: Vec, + // for temporarily storing instructions that will be put after Transaction opcode + constant_insns: Vec, + // Each label has a list of InsnReferences that must + // be resolved. Lists are indexed by: label.abs() - 1 + unresolved_labels: Vec>, + next_insn_label: Option, + // Cursors that are referenced by the program. Indexed by CursorID. + pub cursor_ref: Vec<(Option, Option)>, + // List of deferred label resolutions. Each entry is a pair of (label, insn_reference). + deferred_label_resolutions: Vec<(BranchOffset, InsnReference)>, +} + +impl ProgramBuilder { + pub fn new() -> Self { + Self { + next_free_register: 1, + next_free_label: 0, + next_free_cursor_id: 0, + insns: Vec::new(), + unresolved_labels: Vec::new(), + next_insn_label: None, + cursor_ref: Vec::new(), + constant_insns: Vec::new(), + deferred_label_resolutions: Vec::new(), + } + } + + pub fn alloc_register(&mut self) -> usize { + let reg = self.next_free_register; + self.next_free_register += 1; + reg + } + + pub fn alloc_registers(&mut self, amount: usize) -> usize { + let reg = self.next_free_register; + self.next_free_register += amount; + reg + } + + pub fn next_free_register(&self) -> usize { + self.next_free_register + } + + pub fn alloc_cursor_id( + &mut self, + table_identifier: Option, + table: Option
, + ) -> usize { + let cursor = self.next_free_cursor_id; + self.next_free_cursor_id += 1; + self.cursor_ref.push((table_identifier, table)); + assert!(self.cursor_ref.len() == self.next_free_cursor_id); + cursor + } + + pub fn emit_insn(&mut self, insn: Insn) { + self.insns.push(insn); + if let Some(label) = self.next_insn_label { + self.next_insn_label = None; + self.resolve_label(label, (self.insns.len() - 1) as BranchOffset); + } + } + + // Emit an instruction that will be put at the end of the program (after Transaction statement). + // This is useful for instructions that otherwise will be unnecessarily repeated in a loop. + // Example: In `SELECT * from users where name='John'`, it is unnecessary to set r[1]='John' as we SCAN users table. + // We could simply set it once before the SCAN started. + pub fn mark_last_insn_constant(&mut self) { + self.constant_insns.push(self.insns.pop().unwrap()); + } + + pub fn emit_constant_insns(&mut self) { + self.insns.append(&mut self.constant_insns); + } + + pub fn emit_insn_with_label_dependency(&mut self, insn: Insn, label: BranchOffset) { + self.insns.push(insn); + self.add_label_dependency(label, (self.insns.len() - 1) as BranchOffset); + } + + pub fn offset(&self) -> BranchOffset { + self.insns.len() as BranchOffset + } + + pub fn allocate_label(&mut self) -> BranchOffset { + self.next_free_label -= 1; + self.unresolved_labels.push(Vec::new()); + self.next_free_label + } + + // Effectively a GOTO without the need to emit an explicit GOTO instruction. + // Useful when you know you need to jump to "the next part", but the exact offset is unknowable + // at the time of emitting the instruction. + pub fn preassign_label_to_next_insn(&mut self, label: BranchOffset) { + self.next_insn_label = Some(label); + } + + fn label_to_index(&self, label: BranchOffset) -> usize { + (label.abs() - 1) as usize + } + + pub fn add_label_dependency(&mut self, label: BranchOffset, insn_reference: BranchOffset) { + assert!(insn_reference >= 0); + assert!(label < 0); + let label_index = self.label_to_index(label); + assert!(label_index < self.unresolved_labels.len()); + let insn_reference = insn_reference as InsnReference; + let label_references = &mut self.unresolved_labels[label_index]; + label_references.push(insn_reference); + } + + pub fn defer_label_resolution(&mut self, label: BranchOffset, insn_reference: InsnReference) { + self.deferred_label_resolutions + .push((label, insn_reference)); + } + + pub fn resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset) { + assert!(label < 0); + assert!(to_offset >= 0); + let label_index = self.label_to_index(label); + assert!( + label_index < self.unresolved_labels.len(), + "Forbidden resolve of an unexistent label!" + ); + + let label_references = &mut self.unresolved_labels[label_index]; + for insn_reference in label_references.iter() { + let insn = &mut self.insns[*insn_reference]; + match insn { + Insn::Init { target_pc } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::Eq { + lhs: _lhs, + rhs: _rhs, + target_pc, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::Ne { + lhs: _lhs, + rhs: _rhs, + target_pc, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::Lt { + lhs: _lhs, + rhs: _rhs, + target_pc, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::Le { + lhs: _lhs, + rhs: _rhs, + target_pc, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::Gt { + lhs: _lhs, + rhs: _rhs, + target_pc, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::Ge { + lhs: _lhs, + rhs: _rhs, + target_pc, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::If { + reg: _reg, + target_pc, + null_reg: _, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::IfNot { + reg: _reg, + target_pc, + null_reg: _, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::RewindAwait { + cursor_id: _cursor_id, + pc_if_empty, + } => { + assert!(*pc_if_empty < 0); + *pc_if_empty = to_offset; + } + Insn::Goto { target_pc } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::DecrJumpZero { + reg: _reg, + target_pc, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::SorterNext { + cursor_id: _cursor_id, + pc_if_next, + } => { + assert!(*pc_if_next < 0); + *pc_if_next = to_offset; + } + Insn::SorterSort { pc_if_empty, .. } => { + assert!(*pc_if_empty < 0); + *pc_if_empty = to_offset; + } + Insn::NotNull { + reg: _reg, + target_pc, + } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + Insn::IfPos { target_pc, .. } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } + _ => { + todo!("missing resolve_label for {:?}", insn); + } + } + } + label_references.clear(); + } + + // translate table to cursor id + pub fn resolve_cursor_id( + &self, + table_identifier: &str, + cursor_hint: Option, + ) -> CursorID { + if let Some(cursor_hint) = cursor_hint { + return cursor_hint; + } + self.cursor_ref + .iter() + .position(|(t_ident, _)| { + t_ident + .as_ref() + .is_some_and(|ident| ident == table_identifier) + }) + .unwrap() + } + + pub fn resolve_deferred_labels(&mut self) { + for i in 0..self.deferred_label_resolutions.len() { + let (label, insn_reference) = self.deferred_label_resolutions[i]; + self.resolve_label(label, insn_reference as BranchOffset); + } + self.deferred_label_resolutions.clear(); + } + + pub fn build(self) -> Program { + assert!( + self.deferred_label_resolutions.is_empty(), + "deferred_label_resolutions is not empty when build() is called, did you forget to call resolve_deferred_labels()?" + ); + assert!( + self.constant_insns.is_empty(), + "constant_insns is not empty when build() is called, did you forget to call emit_constant_insns()?" + ); + Program { + max_registers: self.next_free_register, + insns: self.insns, + cursor_ref: self.cursor_ref, + } + } +} \ No newline at end of file diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs new file mode 100644 index 000000000..c895794b4 --- /dev/null +++ b/core/vdbe/explain.rs @@ -0,0 +1,522 @@ +use super::{Program, InsnReference, Insn, OwnedValue}; +use std::rc::Rc; + +pub fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: String) -> String { + let (opcode, p1, p2, p3, p4, p5, comment): (&str, i32, i32, i32, OwnedValue, u16, String) = + match insn { + Insn::Init { target_pc } => ( + "Init", + 0, + *target_pc as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("Start at {}", target_pc), + ), + Insn::Add { lhs, rhs, dest } => ( + "Add", + *lhs as i32, + *rhs as i32, + *dest as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("r[{}]=r[{}]+r[{}]", dest, lhs, rhs), + ), + Insn::Null { dest } => ( + "Null", + *dest as i32, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("r[{}]=NULL", dest), + ), + Insn::NullRow { cursor_id } => ( + "NullRow", + *cursor_id as i32, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("Set cursor {} to a (pseudo) NULL row", cursor_id), + ), + Insn::NotNull { reg, target_pc } => ( + "NotNull", + *reg as i32, + *target_pc as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("r[{}] -> {}", reg, target_pc), + ), + Insn::IfPos { + reg, + target_pc, + decrement_by, + } => ( + "IfPos", + *reg as i32, + *target_pc as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!( + "r[{}]>0 -> r[{}]-={}, goto {}", + reg, reg, decrement_by, target_pc + ), + ), + Insn::Eq { + lhs, + rhs, + target_pc, + } => ( + "Eq", + *lhs as i32, + *rhs as i32, + *target_pc as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if r[{}]==r[{}] goto {}", lhs, rhs, target_pc), + ), + Insn::Ne { + lhs, + rhs, + target_pc, + } => ( + "Ne", + *lhs as i32, + *rhs as i32, + *target_pc as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if r[{}]!=r[{}] goto {}", lhs, rhs, target_pc), + ), + Insn::Lt { + lhs, + rhs, + target_pc, + } => ( + "Lt", + *lhs as i32, + *rhs as i32, + *target_pc as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if r[{}] ( + "Le", + *lhs as i32, + *rhs as i32, + *target_pc as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if r[{}]<=r[{}] goto {}", lhs, rhs, target_pc), + ), + Insn::Gt { + lhs, + rhs, + target_pc, + } => ( + "Gt", + *lhs as i32, + *rhs as i32, + *target_pc as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if r[{}]>r[{}] goto {}", lhs, rhs, target_pc), + ), + Insn::Ge { + lhs, + rhs, + target_pc, + } => ( + "Ge", + *lhs as i32, + *rhs as i32, + *target_pc as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if r[{}]>=r[{}] goto {}", lhs, rhs, target_pc), + ), + Insn::If { + reg, + target_pc, + null_reg, + } => ( + "If", + *reg as i32, + *target_pc as i32, + *null_reg as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if r[{}] goto {}", reg, target_pc), + ), + Insn::IfNot { + reg, + target_pc, + null_reg, + } => ( + "IfNot", + *reg as i32, + *target_pc as i32, + *null_reg as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if !r[{}] goto {}", reg, target_pc), + ), + Insn::OpenReadAsync { + cursor_id, + root_page, + } => ( + "OpenReadAsync", + *cursor_id as i32, + *root_page as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("root={}", root_page), + ), + Insn::OpenReadAwait => ( + "OpenReadAwait", + 0, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::OpenPseudo { + cursor_id, + content_reg, + num_fields, + } => ( + "OpenPseudo", + *cursor_id as i32, + *content_reg as i32, + *num_fields as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("{} columns in r[{}]", num_fields, content_reg), + ), + Insn::RewindAsync { cursor_id } => ( + "RewindAsync", + *cursor_id as i32, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::RewindAwait { + cursor_id, + pc_if_empty, + } => ( + "RewindAwait", + *cursor_id as i32, + *pc_if_empty as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::Column { + cursor_id, + column, + dest, + } => { + let (_, table) = &program.cursor_ref[*cursor_id]; + ( + "Column", + *cursor_id as i32, + *column as i32, + *dest as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!( + "r[{}]={}.{}", + dest, + table + .as_ref() + .map(|x| x.get_name()) + .unwrap_or(format!("cursor {}", cursor_id).as_str()), + table + .as_ref() + .and_then(|x| x.column_index_to_name(*column)) + .unwrap_or(format!("column {}", *column).as_str()) + ), + ) + } + Insn::MakeRecord { + start_reg, + count, + dest_reg, + } => ( + "MakeRecord", + *start_reg as i32, + *count as i32, + *dest_reg as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!( + "r[{}]=mkrec(r[{}..{}])", + dest_reg, + start_reg, + start_reg + count - 1, + ), + ), + Insn::ResultRow { start_reg, count } => ( + "ResultRow", + *start_reg as i32, + *count as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + if *count == 1 { + format!("output=r[{}]", start_reg) + } else { + format!("output=r[{}..{}]", start_reg, start_reg + count - 1) + }, + ), + Insn::NextAsync { cursor_id } => ( + "NextAsync", + *cursor_id as i32, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::NextAwait { + cursor_id, + pc_if_next, + } => ( + "NextAwait", + *cursor_id as i32, + *pc_if_next as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::Halt => ( + "Halt", + 0, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::Transaction => ( + "Transaction", + 0, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::Goto { target_pc } => ( + "Goto", + 0, + *target_pc as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::Integer { value, dest } => ( + "Integer", + *value as i32, + *dest as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("r[{}]={}", dest, value), + ), + Insn::Real { value, dest } => ( + "Real", + 0, + *dest as i32, + 0, + OwnedValue::Float(*value), + 0, + format!("r[{}]={}", dest, value), + ), + Insn::RealAffinity { register } => ( + "RealAffinity", + *register as i32, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::String8 { value, dest } => ( + "String8", + 0, + *dest as i32, + 0, + OwnedValue::Text(Rc::new(value.clone())), + 0, + format!("r[{}]='{}'", dest, value), + ), + Insn::RowId { cursor_id, dest } => ( + "RowId", + *cursor_id as i32, + *dest as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!( + "r[{}]={}.rowid", + dest, + &program.cursor_ref[*cursor_id] + .1 + .as_ref() + .map(|x| x.get_name()) + .unwrap_or(format!("cursor {}", cursor_id).as_str()) + ), + ), + Insn::DecrJumpZero { reg, target_pc } => ( + "DecrJumpZero", + *reg as i32, + *target_pc as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if (--r[{}]==0) goto {}", reg, target_pc), + ), + Insn::AggStep { + func, + acc_reg, + delimiter: _, + col, + } => ( + "AggStep", + 0, + *col as i32, + *acc_reg as i32, + OwnedValue::Text(Rc::new(func.to_string().into())), + 0, + format!("accum=r[{}] step(r[{}])", *acc_reg, *col), + ), + Insn::AggFinal { register, func } => ( + "AggFinal", + 0, + *register as i32, + 0, + OwnedValue::Text(Rc::new(func.to_string().into())), + 0, + format!("accum=r[{}]", *register), + ), + Insn::SorterOpen { + cursor_id, + columns, + order, + } => { + let _p4 = String::new(); + let to_print: Vec = order + .values + .iter() + .map(|v| match v { + OwnedValue::Integer(i) => { + if *i == 0 { + "B".to_string() + } else { + "-B".to_string() + } + } + _ => unreachable!(), + }) + .collect(); + ( + "SorterOpen", + *cursor_id as i32, + *columns as i32, + 0, + OwnedValue::Text(Rc::new(format!("k({},{})", columns, to_print.join(",")))), + 0, + format!("cursor={}", cursor_id), + ) + } + Insn::SorterData { + cursor_id, + dest_reg, + pseudo_cursor, + } => ( + "SorterData", + *cursor_id as i32, + *dest_reg as i32, + *pseudo_cursor as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("r[{}]=data", dest_reg), + ), + Insn::SorterInsert { + cursor_id, + record_reg, + } => ( + "SorterInsert", + *cursor_id as i32, + *record_reg as i32, + 0, + OwnedValue::Integer(0), + 0, + format!("key=r[{}]", record_reg), + ), + Insn::SorterSort { + cursor_id, + pc_if_empty, + } => ( + "SorterSort", + *cursor_id as i32, + *pc_if_empty as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::SorterNext { + cursor_id, + pc_if_next, + } => ( + "SorterNext", + *cursor_id as i32, + *pc_if_next as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::Function { + start_reg, + dest, + func, + } => ( + "Function", + 1, + *start_reg as i32, + *dest as i32, + OwnedValue::Text(Rc::new(func.to_string())), + 0, + format!("r[{}]=func(r[{}..])", dest, start_reg), + ), + }; + format!( + "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", + addr, + &(indent + opcode), + p1, + p2, + p3, + p4.to_string(), + p5, + comment + ) +} diff --git a/core/vdbe.rs b/core/vdbe/mod.rs similarity index 67% rename from core/vdbe.rs rename to core/vdbe/mod.rs index 68ddb5fff..575dbb6bc 100644 --- a/core/vdbe.rs +++ b/core/vdbe/mod.rs @@ -1,3 +1,7 @@ +pub mod builder; +pub mod explain; +pub mod sorter; + use crate::btree::BTreeCursor; use crate::function::{AggFunc, SingleRowFunc}; use crate::pager::Pager; @@ -259,300 +263,6 @@ pub enum Insn { // Index of insn in list of insns type InsnReference = usize; -pub struct ProgramBuilder { - next_free_register: usize, - next_free_label: BranchOffset, - next_free_cursor_id: usize, - insns: Vec, - // for temporarily storing instructions that will be put after Transaction opcode - constant_insns: Vec, - // Each label has a list of InsnReferences that must - // be resolved. Lists are indexed by: label.abs() - 1 - unresolved_labels: Vec>, - next_insn_label: Option, - // Cursors that are referenced by the program. Indexed by CursorID. - pub cursor_ref: Vec<(Option, Option
)>, - // List of deferred label resolutions. Each entry is a pair of (label, insn_reference). - deferred_label_resolutions: Vec<(BranchOffset, InsnReference)>, -} - -impl ProgramBuilder { - pub fn new() -> Self { - Self { - next_free_register: 1, - next_free_label: 0, - next_free_cursor_id: 0, - insns: Vec::new(), - unresolved_labels: Vec::new(), - next_insn_label: None, - cursor_ref: Vec::new(), - constant_insns: Vec::new(), - deferred_label_resolutions: Vec::new(), - } - } - - pub fn alloc_register(&mut self) -> usize { - let reg = self.next_free_register; - self.next_free_register += 1; - reg - } - - pub fn alloc_registers(&mut self, amount: usize) -> usize { - let reg = self.next_free_register; - self.next_free_register += amount; - reg - } - - pub fn next_free_register(&self) -> usize { - self.next_free_register - } - - pub fn alloc_cursor_id( - &mut self, - table_identifier: Option, - table: Option
, - ) -> usize { - let cursor = self.next_free_cursor_id; - self.next_free_cursor_id += 1; - self.cursor_ref.push((table_identifier, table)); - assert!(self.cursor_ref.len() == self.next_free_cursor_id); - cursor - } - - pub fn emit_insn(&mut self, insn: Insn) { - self.insns.push(insn); - if let Some(label) = self.next_insn_label { - self.next_insn_label = None; - self.resolve_label(label, (self.insns.len() - 1) as BranchOffset); - } - } - - // Emit an instruction that will be put at the end of the program (after Transaction statement). - // This is useful for instructions that otherwise will be unnecessarily repeated in a loop. - // Example: In `SELECT * from users where name='John'`, it is unnecessary to set r[1]='John' as we SCAN users table. - // We could simply set it once before the SCAN started. - pub fn mark_last_insn_constant(&mut self) { - self.constant_insns.push(self.insns.pop().unwrap()); - } - - pub fn emit_constant_insns(&mut self) { - self.insns.append(&mut self.constant_insns); - } - - pub fn emit_insn_with_label_dependency(&mut self, insn: Insn, label: BranchOffset) { - self.insns.push(insn); - self.add_label_dependency(label, (self.insns.len() - 1) as BranchOffset); - } - - pub fn offset(&self) -> BranchOffset { - self.insns.len() as BranchOffset - } - - pub fn allocate_label(&mut self) -> BranchOffset { - self.next_free_label -= 1; - self.unresolved_labels.push(Vec::new()); - self.next_free_label - } - - // Effectively a GOTO without the need to emit an explicit GOTO instruction. - // Useful when you know you need to jump to "the next part", but the exact offset is unknowable - // at the time of emitting the instruction. - pub fn preassign_label_to_next_insn(&mut self, label: BranchOffset) { - self.next_insn_label = Some(label); - } - - fn label_to_index(&self, label: BranchOffset) -> usize { - (label.abs() - 1) as usize - } - - pub fn add_label_dependency(&mut self, label: BranchOffset, insn_reference: BranchOffset) { - assert!(insn_reference >= 0); - assert!(label < 0); - let label_index = self.label_to_index(label); - assert!(label_index < self.unresolved_labels.len()); - let insn_reference = insn_reference as InsnReference; - let label_references = &mut self.unresolved_labels[label_index]; - label_references.push(insn_reference); - } - - pub fn defer_label_resolution(&mut self, label: BranchOffset, insn_reference: InsnReference) { - self.deferred_label_resolutions - .push((label, insn_reference)); - } - - pub fn resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset) { - assert!(label < 0); - assert!(to_offset >= 0); - let label_index = self.label_to_index(label); - assert!( - label_index < self.unresolved_labels.len(), - "Forbidden resolve of an unexistent label!" - ); - - let label_references = &mut self.unresolved_labels[label_index]; - for insn_reference in label_references.iter() { - let insn = &mut self.insns[*insn_reference]; - match insn { - Insn::Init { target_pc } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::Eq { - lhs: _lhs, - rhs: _rhs, - target_pc, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::Ne { - lhs: _lhs, - rhs: _rhs, - target_pc, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::Lt { - lhs: _lhs, - rhs: _rhs, - target_pc, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::Le { - lhs: _lhs, - rhs: _rhs, - target_pc, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::Gt { - lhs: _lhs, - rhs: _rhs, - target_pc, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::Ge { - lhs: _lhs, - rhs: _rhs, - target_pc, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::If { - reg: _reg, - target_pc, - null_reg: _, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::IfNot { - reg: _reg, - target_pc, - null_reg: _, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::RewindAwait { - cursor_id: _cursor_id, - pc_if_empty, - } => { - assert!(*pc_if_empty < 0); - *pc_if_empty = to_offset; - } - Insn::Goto { target_pc } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::DecrJumpZero { - reg: _reg, - target_pc, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::SorterNext { - cursor_id: _cursor_id, - pc_if_next, - } => { - assert!(*pc_if_next < 0); - *pc_if_next = to_offset; - } - Insn::SorterSort { pc_if_empty, .. } => { - assert!(*pc_if_empty < 0); - *pc_if_empty = to_offset; - } - Insn::NotNull { - reg: _reg, - target_pc, - } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - Insn::IfPos { target_pc, .. } => { - assert!(*target_pc < 0); - *target_pc = to_offset; - } - _ => { - todo!("missing resolve_label for {:?}", insn); - } - } - } - label_references.clear(); - } - - // translate table to cursor id - pub fn resolve_cursor_id( - &self, - table_identifier: &str, - cursor_hint: Option, - ) -> CursorID { - if let Some(cursor_hint) = cursor_hint { - return cursor_hint; - } - self.cursor_ref - .iter() - .position(|(t_ident, _)| { - t_ident - .as_ref() - .is_some_and(|ident| ident == table_identifier) - }) - .unwrap() - } - - pub fn resolve_deferred_labels(&mut self) { - for i in 0..self.deferred_label_resolutions.len() { - let (label, insn_reference) = self.deferred_label_resolutions[i]; - self.resolve_label(label, insn_reference as BranchOffset); - } - self.deferred_label_resolutions.clear(); - } - - pub fn build(self) -> Program { - assert!( - self.deferred_label_resolutions.is_empty(), - "deferred_label_resolutions is not empty when build() is called, did you forget to call resolve_deferred_labels()?" - ); - assert!( - self.constant_insns.is_empty(), - "constant_insns is not empty when build() is called, did you forget to call emit_constant_insns()?" - ); - Program { - max_registers: self.next_free_register, - insns: self.insns, - cursor_ref: self.cursor_ref, - } - } -} - pub enum StepResult<'a> { Done, IO, @@ -1218,7 +928,7 @@ impl Program { _ => unreachable!(), }) .collect(); - let cursor = Box::new(crate::sorter::Sorter::new(order)); + let cursor = Box::new(sorter::Sorter::new(order)); cursors.insert(*cursor_id, cursor); state.pc += 1; } @@ -1414,534 +1124,14 @@ fn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) { if !log::log_enabled!(log::Level::Trace) { return; } - log::trace!("{}", insn_to_str(program, addr, insn, String::new())); + log::trace!("{}", explain::insn_to_str(program, addr, insn, String::new())); } fn print_insn(program: &Program, addr: InsnReference, insn: &Insn, indent: String) { - let s = insn_to_str(program, addr, insn, indent); + let s = explain::insn_to_str(program, addr, insn, indent); println!("{}", s); } -fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: String) -> String { - let (opcode, p1, p2, p3, p4, p5, comment): (&str, i32, i32, i32, OwnedValue, u16, String) = - match insn { - Insn::Init { target_pc } => ( - "Init", - 0, - *target_pc as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("Start at {}", target_pc), - ), - Insn::Add { lhs, rhs, dest } => ( - "Add", - *lhs as i32, - *rhs as i32, - *dest as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("r[{}]=r[{}]+r[{}]", dest, lhs, rhs), - ), - Insn::Null { dest } => ( - "Null", - *dest as i32, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("r[{}]=NULL", dest), - ), - Insn::NullRow { cursor_id } => ( - "NullRow", - *cursor_id as i32, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("Set cursor {} to a (pseudo) NULL row", cursor_id), - ), - Insn::NotNull { reg, target_pc } => ( - "NotNull", - *reg as i32, - *target_pc as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("r[{}] -> {}", reg, target_pc), - ), - Insn::IfPos { - reg, - target_pc, - decrement_by, - } => ( - "IfPos", - *reg as i32, - *target_pc as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!( - "r[{}]>0 -> r[{}]-={}, goto {}", - reg, reg, decrement_by, target_pc - ), - ), - Insn::Eq { - lhs, - rhs, - target_pc, - } => ( - "Eq", - *lhs as i32, - *rhs as i32, - *target_pc as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if r[{}]==r[{}] goto {}", lhs, rhs, target_pc), - ), - Insn::Ne { - lhs, - rhs, - target_pc, - } => ( - "Ne", - *lhs as i32, - *rhs as i32, - *target_pc as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if r[{}]!=r[{}] goto {}", lhs, rhs, target_pc), - ), - Insn::Lt { - lhs, - rhs, - target_pc, - } => ( - "Lt", - *lhs as i32, - *rhs as i32, - *target_pc as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if r[{}] ( - "Le", - *lhs as i32, - *rhs as i32, - *target_pc as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if r[{}]<=r[{}] goto {}", lhs, rhs, target_pc), - ), - Insn::Gt { - lhs, - rhs, - target_pc, - } => ( - "Gt", - *lhs as i32, - *rhs as i32, - *target_pc as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if r[{}]>r[{}] goto {}", lhs, rhs, target_pc), - ), - Insn::Ge { - lhs, - rhs, - target_pc, - } => ( - "Ge", - *lhs as i32, - *rhs as i32, - *target_pc as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if r[{}]>=r[{}] goto {}", lhs, rhs, target_pc), - ), - Insn::If { - reg, - target_pc, - null_reg, - } => ( - "If", - *reg as i32, - *target_pc as i32, - *null_reg as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if r[{}] goto {}", reg, target_pc), - ), - Insn::IfNot { - reg, - target_pc, - null_reg, - } => ( - "IfNot", - *reg as i32, - *target_pc as i32, - *null_reg as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if !r[{}] goto {}", reg, target_pc), - ), - Insn::OpenReadAsync { - cursor_id, - root_page, - } => ( - "OpenReadAsync", - *cursor_id as i32, - *root_page as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("root={}", root_page), - ), - Insn::OpenReadAwait => ( - "OpenReadAwait", - 0, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::OpenPseudo { - cursor_id, - content_reg, - num_fields, - } => ( - "OpenPseudo", - *cursor_id as i32, - *content_reg as i32, - *num_fields as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("{} columns in r[{}]", num_fields, content_reg), - ), - Insn::RewindAsync { cursor_id } => ( - "RewindAsync", - *cursor_id as i32, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::RewindAwait { - cursor_id, - pc_if_empty, - } => ( - "RewindAwait", - *cursor_id as i32, - *pc_if_empty as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::Column { - cursor_id, - column, - dest, - } => { - let (_, table) = &program.cursor_ref[*cursor_id]; - ( - "Column", - *cursor_id as i32, - *column as i32, - *dest as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!( - "r[{}]={}.{}", - dest, - table - .as_ref() - .map(|x| x.get_name()) - .unwrap_or(format!("cursor {}", cursor_id).as_str()), - table - .as_ref() - .and_then(|x| x.column_index_to_name(*column)) - .unwrap_or(format!("column {}", *column).as_str()) - ), - ) - } - Insn::MakeRecord { - start_reg, - count, - dest_reg, - } => ( - "MakeRecord", - *start_reg as i32, - *count as i32, - *dest_reg as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!( - "r[{}]=mkrec(r[{}..{}])", - dest_reg, - start_reg, - start_reg + count - 1, - ), - ), - Insn::ResultRow { start_reg, count } => ( - "ResultRow", - *start_reg as i32, - *count as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - if *count == 1 { - format!("output=r[{}]", start_reg) - } else { - format!("output=r[{}..{}]", start_reg, start_reg + count - 1) - }, - ), - Insn::NextAsync { cursor_id } => ( - "NextAsync", - *cursor_id as i32, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::NextAwait { - cursor_id, - pc_if_next, - } => ( - "NextAwait", - *cursor_id as i32, - *pc_if_next as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::Halt => ( - "Halt", - 0, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::Transaction => ( - "Transaction", - 0, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::Goto { target_pc } => ( - "Goto", - 0, - *target_pc as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::Integer { value, dest } => ( - "Integer", - *value as i32, - *dest as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("r[{}]={}", dest, value), - ), - Insn::Real { value, dest } => ( - "Real", - 0, - *dest as i32, - 0, - OwnedValue::Float(*value), - 0, - format!("r[{}]={}", dest, value), - ), - Insn::RealAffinity { register } => ( - "RealAffinity", - *register as i32, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::String8 { value, dest } => ( - "String8", - 0, - *dest as i32, - 0, - OwnedValue::Text(Rc::new(value.clone())), - 0, - format!("r[{}]='{}'", dest, value), - ), - Insn::RowId { cursor_id, dest } => ( - "RowId", - *cursor_id as i32, - *dest as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!( - "r[{}]={}.rowid", - dest, - &program.cursor_ref[*cursor_id] - .1 - .as_ref() - .map(|x| x.get_name()) - .unwrap_or(format!("cursor {}", cursor_id).as_str()) - ), - ), - Insn::DecrJumpZero { reg, target_pc } => ( - "DecrJumpZero", - *reg as i32, - *target_pc as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("if (--r[{}]==0) goto {}", reg, target_pc), - ), - Insn::AggStep { - func, - acc_reg, - delimiter: _, - col, - } => ( - "AggStep", - 0, - *col as i32, - *acc_reg as i32, - OwnedValue::Text(Rc::new(func.to_string().into())), - 0, - format!("accum=r[{}] step(r[{}])", *acc_reg, *col), - ), - Insn::AggFinal { register, func } => ( - "AggFinal", - 0, - *register as i32, - 0, - OwnedValue::Text(Rc::new(func.to_string().into())), - 0, - format!("accum=r[{}]", *register), - ), - Insn::SorterOpen { - cursor_id, - columns, - order, - } => { - let _p4 = String::new(); - let to_print: Vec = order - .values - .iter() - .map(|v| match v { - OwnedValue::Integer(i) => { - if *i == 0 { - "B".to_string() - } else { - "-B".to_string() - } - } - _ => unreachable!(), - }) - .collect(); - ( - "SorterOpen", - *cursor_id as i32, - *columns as i32, - 0, - OwnedValue::Text(Rc::new(format!("k({},{})", columns, to_print.join(",")))), - 0, - format!("cursor={}", cursor_id), - ) - } - Insn::SorterData { - cursor_id, - dest_reg, - pseudo_cursor, - } => ( - "SorterData", - *cursor_id as i32, - *dest_reg as i32, - *pseudo_cursor as i32, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("r[{}]=data", dest_reg), - ), - Insn::SorterInsert { - cursor_id, - record_reg, - } => ( - "SorterInsert", - *cursor_id as i32, - *record_reg as i32, - 0, - OwnedValue::Integer(0), - 0, - format!("key=r[{}]", record_reg), - ), - Insn::SorterSort { - cursor_id, - pc_if_empty, - } => ( - "SorterSort", - *cursor_id as i32, - *pc_if_empty as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::SorterNext { - cursor_id, - pc_if_next, - } => ( - "SorterNext", - *cursor_id as i32, - *pc_if_next as i32, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - "".to_string(), - ), - Insn::Function { - start_reg, - dest, - func, - } => ( - "Function", - 1, - *start_reg as i32, - *dest as i32, - OwnedValue::Text(Rc::new(func.to_string())), - 0, - format!("r[{}]=func(r[{}..])", dest, start_reg), - ), - }; - format!( - "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", - addr, - &(indent + opcode), - p1, - p2, - p3, - p4.to_string(), - p5, - comment - ) -} - fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&Insn>) -> usize { let indent_count = if let Some(insn) = prev_insn { match insn { diff --git a/core/sorter.rs b/core/vdbe/sorter.rs similarity index 100% rename from core/sorter.rs rename to core/vdbe/sorter.rs