mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-08 17:54:22 +01:00
@@ -5,7 +5,6 @@ mod io;
|
||||
mod pager;
|
||||
mod pseudo;
|
||||
mod schema;
|
||||
mod sorter;
|
||||
mod sqlite3_ondisk;
|
||||
mod storage;
|
||||
mod translate;
|
||||
|
||||
@@ -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<Select<'a>> {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
295
core/vdbe/builder.rs
Normal file
295
core/vdbe/builder.rs
Normal file
@@ -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<Insn>,
|
||||
// for temporarily storing instructions that will be put after Transaction opcode
|
||||
constant_insns: Vec<Insn>,
|
||||
// Each label has a list of InsnReferences that must
|
||||
// be resolved. Lists are indexed by: label.abs() - 1
|
||||
unresolved_labels: Vec<Vec<InsnReference>>,
|
||||
next_insn_label: Option<BranchOffset>,
|
||||
// Cursors that are referenced by the program. Indexed by CursorID.
|
||||
pub cursor_ref: Vec<(Option<String>, Option<Table>)>,
|
||||
// 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<String>,
|
||||
table: Option<Table>,
|
||||
) -> 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 <next insn> 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>,
|
||||
) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
522
core/vdbe/explain.rs
Normal file
522
core/vdbe/explain.rs
Normal file
@@ -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[{}]<r[{}] goto {}", lhs, rhs, target_pc),
|
||||
),
|
||||
Insn::Le {
|
||||
lhs,
|
||||
rhs,
|
||||
target_pc,
|
||||
} => (
|
||||
"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<String> = 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
|
||||
)
|
||||
}
|
||||
@@ -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<Insn>,
|
||||
// for temporarily storing instructions that will be put after Transaction opcode
|
||||
constant_insns: Vec<Insn>,
|
||||
// Each label has a list of InsnReferences that must
|
||||
// be resolved. Lists are indexed by: label.abs() - 1
|
||||
unresolved_labels: Vec<Vec<InsnReference>>,
|
||||
next_insn_label: Option<BranchOffset>,
|
||||
// Cursors that are referenced by the program. Indexed by CursorID.
|
||||
pub cursor_ref: Vec<(Option<String>, Option<Table>)>,
|
||||
// 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<String>,
|
||||
table: Option<Table>,
|
||||
) -> 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 <next insn> 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>,
|
||||
) -> 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[{}]<r[{}] goto {}", lhs, rhs, target_pc),
|
||||
),
|
||||
Insn::Le {
|
||||
lhs,
|
||||
rhs,
|
||||
target_pc,
|
||||
} => (
|
||||
"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<String> = 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 {
|
||||
Reference in New Issue
Block a user