Merge 'Add ability to annotate instructions with comments' from Jussi Saurio

Similar to what SQLite does.

```
limbo> EXPLAIN SELECT u.age, p.name FROM users u LEFT JOIN products p ON u.first_name = p.name;
addr  opcode             p1    p2    p3    p4             p5  comment
----  -----------------  ----  ----  ----  -------------  --  -------
0     Init               0     25    0                    0   Start at 25
1     OpenReadAsync      0     2     0                    0   table=u, root=2
2     OpenReadAwait      0     0     0                    0
3     OpenReadAsync      1     3     0                    0   table=p, root=3
4     OpenReadAwait      0     0     0                    0
5     RewindAsync        0     0     0                    0
6     RewindAwait        0     -5    0                    0   Rewind table u
7       Integer          0     1     0                    0   r[1]=0; init LEFT JOIN match flag
8       RewindAsync      1     0     0                    0
9       RewindAwait      1     -11   0                    0   Rewind table p
10        Column         0     1     2                    0   r[2]=u.first_name
11        Column         1     1     3                    0   r[3]=p.name
12        Ne             2     3     17                   0   if r[2]!=r[3] goto 17
13        Integer        1     1     0                    0   r[1]=1; record LEFT JOIN hit
14        Column         0     9     4                    0   r[4]=u.age
15        Column         1     1     5                    0   r[5]=p.name
16        ResultRow      4     2     0                    0   output=r[4..5]
17      NextAsync        1     0     0                    0
18      NextAwait        1     9     0                    0
19      IfPos            1     22    0                    0   r[1]>0 -> r[1]-=0, goto 22
20      NullRow          1     0     0                    0   Set cursor 1 to a (pseudo) NULL row
21      Goto             0     13    0                    0
22    NextAsync          0     0     0                    0
23    NextAwait          0     6     0                    0
24    Halt               0     0     0                    0
25    Transaction        0     0     0                    0
26    Goto               0     1     0                    0
```

Closes #258
This commit is contained in:
Pekka Enberg
2024-08-01 21:07:31 +03:00
4 changed files with 35 additions and 6 deletions

View File

@@ -650,6 +650,7 @@ fn translate_table_open_cursor(
* if condition checks pass, it will eventually be set to true
*/
fn left_join_match_flag_initialize(program: &mut ProgramBuilder, left_join: &LeftJoinBookkeeping) {
program.add_comment(program.offset(), "init LEFT JOIN match flag");
program.emit_insn(Insn::Integer {
value: 0,
dest: left_join.match_flag_register,
@@ -664,6 +665,7 @@ fn left_join_match_flag_set_true(program: &mut ProgramBuilder, left_join: &LeftJ
left_join.set_match_flag_true_label,
program.offset() as usize,
);
program.add_comment(program.offset(), "record LEFT JOIN hit");
program.emit_insn(Insn::Integer {
value: 1,
dest: left_join.match_flag_register,

View File

@@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::storage::sqlite3_ondisk::DatabaseHeader;
@@ -21,6 +21,8 @@ pub struct ProgramBuilder {
deferred_label_resolutions: Vec<(BranchOffset, InsnReference)>,
// Bitmask of cursors that have emitted a SeekRowid instruction.
seekrowid_emitted_bitmask: u64,
// map of instruction index to manual comment (used in EXPLAIN)
comments: HashMap<BranchOffset, &'static str>,
}
impl ProgramBuilder {
@@ -36,6 +38,7 @@ impl ProgramBuilder {
constant_insns: Vec::new(),
deferred_label_resolutions: Vec::new(),
seekrowid_emitted_bitmask: 0,
comments: HashMap::new(),
}
}
@@ -90,6 +93,10 @@ impl ProgramBuilder {
}
}
pub fn add_comment(&mut self, insn_index: BranchOffset, comment: &'static str) {
self.comments.insert(insn_index, comment);
}
// 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.
@@ -341,6 +348,7 @@ impl ProgramBuilder {
insns: self.insns,
cursor_ref: self.cursor_ref,
database_header,
comments: self.comments,
}
}
}

View File

@@ -1,7 +1,13 @@
use super::{Insn, InsnReference, OwnedValue, Program};
use std::rc::Rc;
pub fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: String) -> String {
pub fn insn_to_str(
program: &Program,
addr: InsnReference,
insn: &Insn,
indent: String,
manual_comment: Option<&'static str>,
) -> String {
let (opcode, p1, p2, p3, p4, p5, comment): (&str, i32, i32, i32, OwnedValue, u16, String) =
match insn {
Insn::Init { target_pc } => (
@@ -680,6 +686,6 @@ pub fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent:
p3,
p4.to_string(),
p5,
comment
manual_comment.map_or(format!("{}", comment), |mc| format!("{}; {}", comment, mc))
)
}

View File

@@ -34,7 +34,7 @@ use crate::Result;
use regex::Regex;
use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;
pub type BranchOffset = i64;
@@ -389,6 +389,7 @@ pub struct Program {
pub insns: Vec<Insn>,
pub cursor_ref: Vec<(Option<String>, Option<Table>)>,
pub database_header: Rc<RefCell<DatabaseHeader>>,
pub comments: HashMap<BranchOffset, &'static str>,
}
impl Program {
@@ -1468,12 +1469,24 @@ fn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) {
}
log::trace!(
"{}",
explain::insn_to_str(program, addr, insn, String::new())
explain::insn_to_str(
program,
addr,
insn,
String::new(),
program.comments.get(&(addr as BranchOffset)).copied()
)
);
}
fn print_insn(program: &Program, addr: InsnReference, insn: &Insn, indent: String) {
let s = explain::insn_to_str(program, addr, insn, indent);
let s = explain::insn_to_str(
program,
addr,
insn,
indent,
program.comments.get(&(addr as BranchOffset)).copied(),
);
println!("{}", s);
}