mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-19 23:15:28 +01:00
Merge 'Add BeginSubrtn, NotFound and Affinity bytecodes' from Diego Reis
I'm working on an optimization of `WHERE .. IN (..)` statements that
requires these bytecodes.
```sh
sqlite> explain select * from users where first_name in ('alice', 'bob', 'charlie');
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Init 0 35 0 0 Start at 35
1 OpenRead 0 2 0 10 0 root=2 iDb=0; users
2 Rewind 0 34 0 0
3 Noop 0 0 0 0 begin IN expr
4 BeginSubrtn 0 1 0 0 r[1]=NULL <---- Here
5 Once 0 17 0 0
6 OpenEphemeral 1 1 0 k(1,B) 0 nColumn=1; RHS of IN operator
7 String8 0 2 0 alice 0 r[2]='alice'
8 MakeRecord 2 1 3 B 0 r[3]=mkrec(r[2])
9 IdxInsert 1 3 2 1 0 key=r[3]
10 String8 0 2 0 bob 0 r[2]='bob'
11 MakeRecord 2 1 3 B 0 r[3]=mkrec(r[2])
12 IdxInsert 1 3 2 1 0 key=r[3]
13 String8 0 2 0 charlie 0 r[2]='charlie'
14 MakeRecord 2 1 3 B 0 r[3]=mkrec(r[2])
15 IdxInsert 1 3 2 1 0 key=r[3]
16 NullRow 1 0 0 0
17 Return 1 5 1 0
18 Column 0 1 4 0 r[4]= cursor 0 column 1
19 IsNull 4 33 0 0 if r[4]==NULL goto 33
20 Affinity 4 1 0 B 0 affinity(r[4]) <---- Here
21 NotFound 1 33 4 1 0 key=r[4]; end IN expr <---- Here
22 Rowid 0 5 0 0 r[5]=users.rowid
23 Column 0 1 6 0 r[6]= cursor 0 column 1
24 Column 0 2 7 0 r[7]= cursor 0 column 2
25 Column 0 3 8 0 r[8]= cursor 0 column 3
26 Column 0 4 9 0 r[9]= cursor 0 column 4
27 Column 0 5 10 0 r[10]= cursor 0 column 5
28 Column 0 6 11 0 r[11]= cursor 0 column 6
29 Column 0 7 12 0 r[12]= cursor 0 column 7
30 Column 0 8 13 0 r[13]= cursor 0 column 8
31 Column 0 9 14 0 r[14]= cursor 0 column 9
32 ResultRow 5 10 0 0 output=r[5..14]
33 Next 0 3 0 1
34 Halt 0 0 0 0
35 Transaction 0 0 3 0 1 usesStmtJournal=0
36 Goto 0 1 0 0
```
EDIT: [Found](https://sqlite.org/opcode.html#Found) and
[NoConflict](https://sqlite.org/opcode.html#NoConflict) can be easily
derived from `NotFound` but I wanted to be concise, I could do it in
another PR :)
Closes #1345
This commit is contained in:
@@ -427,6 +427,7 @@ Modifiers:
|
||||
| BitNot | Yes | |
|
||||
| BitOr | Yes | |
|
||||
| Blob | Yes | |
|
||||
| BeginSubrtn | Yes | |
|
||||
| Checkpoint | No | |
|
||||
| Clear | No | |
|
||||
| Close | No | |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::VirtualTable;
|
||||
use crate::{util::normalize_ident, Result};
|
||||
use crate::{LimboError, VirtualTable};
|
||||
use core::fmt;
|
||||
use fallible_iterator::FallibleIterator;
|
||||
use limbo_sqlite3_parser::ast::{Expr, Literal, SortOrder, TableOptions};
|
||||
@@ -585,6 +585,20 @@ impl Affinity {
|
||||
Affinity::Numeric => SQLITE_AFF_NUMERIC,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_char(char: char) -> Result<Self> {
|
||||
match char {
|
||||
SQLITE_AFF_INTEGER => Ok(Affinity::Integer),
|
||||
SQLITE_AFF_TEXT => Ok(Affinity::Text),
|
||||
SQLITE_AFF_NONE => Ok(Affinity::Blob),
|
||||
SQLITE_AFF_REAL => Ok(Affinity::Real),
|
||||
SQLITE_AFF_NUMERIC => Ok(Affinity::Numeric),
|
||||
_ => Err(LimboError::InternalError(format!(
|
||||
"Invalid affinity character: {}",
|
||||
char
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
|
||||
@@ -4494,6 +4494,87 @@ pub fn op_once(
|
||||
Ok(InsnFunctionStepResult::Step)
|
||||
}
|
||||
|
||||
pub fn op_not_found(
|
||||
program: &Program,
|
||||
state: &mut ProgramState,
|
||||
insn: &Insn,
|
||||
pager: &Rc<Pager>,
|
||||
mv_store: Option<&Rc<MvStore>>,
|
||||
) -> Result<InsnFunctionStepResult> {
|
||||
let Insn::NotFound {
|
||||
cursor_id,
|
||||
target_pc,
|
||||
record_reg,
|
||||
num_regs,
|
||||
} = insn
|
||||
else {
|
||||
unreachable!("unexpected Insn {:?}", insn)
|
||||
};
|
||||
|
||||
let found = {
|
||||
let mut cursor = state.get_cursor(*cursor_id);
|
||||
let cursor = cursor.as_btree_mut();
|
||||
|
||||
if *num_regs == 0 {
|
||||
let record = match &state.registers[*record_reg] {
|
||||
Register::Record(r) => r,
|
||||
_ => {
|
||||
return Err(LimboError::InternalError(
|
||||
"NotFound: exepected a record in the register".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
return_if_io!(cursor.seek(SeekKey::IndexKey(&record), SeekOp::EQ))
|
||||
} else {
|
||||
let record = make_record(&state.registers, record_reg, num_regs);
|
||||
return_if_io!(cursor.seek(SeekKey::IndexKey(&record), SeekOp::EQ))
|
||||
}
|
||||
};
|
||||
|
||||
if found {
|
||||
state.pc += 1;
|
||||
} else {
|
||||
state.pc = target_pc.to_offset_int();
|
||||
}
|
||||
|
||||
Ok(InsnFunctionStepResult::Step)
|
||||
}
|
||||
|
||||
pub fn op_affinity(
|
||||
program: &Program,
|
||||
state: &mut ProgramState,
|
||||
insn: &Insn,
|
||||
pager: &Rc<Pager>,
|
||||
mv_store: Option<&Rc<MvStore>>,
|
||||
) -> Result<InsnFunctionStepResult> {
|
||||
let Insn::Affinity {
|
||||
start_reg,
|
||||
count,
|
||||
affinities,
|
||||
} = insn
|
||||
else {
|
||||
unreachable!("unexpected Insn {:?}", insn)
|
||||
};
|
||||
|
||||
if affinities.len() != count.get() {
|
||||
return Err(LimboError::InternalError(
|
||||
"Affinity: the length of affinities does not match the count".into(),
|
||||
));
|
||||
}
|
||||
|
||||
for (i, affinity_char) in affinities.chars().enumerate().take(count.get()) {
|
||||
let reg_index = *start_reg + i;
|
||||
|
||||
let affinity = Affinity::from_char(affinity_char)?;
|
||||
|
||||
apply_affinity_char(&mut state.registers[reg_index], affinity);
|
||||
}
|
||||
|
||||
state.pc += 1;
|
||||
Ok(InsnFunctionStepResult::Step)
|
||||
}
|
||||
|
||||
fn exec_lower(reg: &OwnedValue) -> Option<OwnedValue> {
|
||||
match reg {
|
||||
OwnedValue::Text(t) => Some(OwnedValue::build_text(&t.as_str().to_lowercase())),
|
||||
|
||||
@@ -1366,6 +1366,57 @@ pub fn insn_to_str(
|
||||
0,
|
||||
format!("goto {}", target_pc_when_reentered.to_debug_int()),
|
||||
),
|
||||
Insn::BeginSubrtn { dest, dest_end } => (
|
||||
"BeginSubrtn",
|
||||
*dest as i32,
|
||||
dest_end.map_or(0, |end| end as i32),
|
||||
0,
|
||||
OwnedValue::build_text(""),
|
||||
0,
|
||||
dest_end.map_or(format!("r[{}]=NULL", dest), |end| {
|
||||
format!("r[{}..{}]=NULL", dest, end)
|
||||
}),
|
||||
),
|
||||
Insn::NotFound {
|
||||
cursor_id,
|
||||
target_pc,
|
||||
record_reg,
|
||||
..
|
||||
} => (
|
||||
"NotFound",
|
||||
*cursor_id as i32,
|
||||
target_pc.to_debug_int(),
|
||||
*record_reg as i32,
|
||||
OwnedValue::build_text(""),
|
||||
0,
|
||||
format!(
|
||||
"if (r[{}] != NULL) goto {}",
|
||||
record_reg,
|
||||
target_pc.to_debug_int()
|
||||
),
|
||||
),
|
||||
Insn::Affinity {
|
||||
start_reg,
|
||||
count,
|
||||
affinities,
|
||||
} => (
|
||||
"Affinity",
|
||||
*start_reg as i32,
|
||||
count.get() as i32,
|
||||
0,
|
||||
OwnedValue::build_text(""),
|
||||
0,
|
||||
format!(
|
||||
"r[{}..{}] = {}",
|
||||
start_reg,
|
||||
start_reg + count.get(),
|
||||
affinities
|
||||
.chars()
|
||||
.map(|a| a.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
),
|
||||
};
|
||||
format!(
|
||||
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::{num::NonZero, rc::Rc};
|
||||
use std::{
|
||||
num::{NonZero, NonZeroUsize},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use super::{execute, AggFunc, BranchOffset, CursorID, FuncCtx, InsnFunction, PageIdx};
|
||||
use crate::{
|
||||
@@ -100,6 +103,12 @@ pub enum Insn {
|
||||
dest: usize,
|
||||
dest_end: Option<usize>,
|
||||
},
|
||||
/// Mark the beginning of a subroutine tha can be entered in-line. This opcode is identical to Null
|
||||
/// it has a different name only to make the byte code easier to read and verify
|
||||
BeginSubrtn {
|
||||
dest: usize,
|
||||
dest_end: Option<usize>,
|
||||
},
|
||||
/// Move the cursor P1 to a null row. Any Column operations that occur while the cursor is on the null row will always write a NULL.
|
||||
NullRow {
|
||||
cursor_id: CursorID,
|
||||
@@ -801,6 +810,25 @@ pub enum Insn {
|
||||
Once {
|
||||
target_pc_when_reentered: BranchOffset,
|
||||
},
|
||||
/// Search for record in the index cusor, if any entry for which the key is a prefix exists
|
||||
/// is a no-op, otherwise go to target_pc
|
||||
/// Example =>
|
||||
/// For a index key (1,2,3):
|
||||
/// NotFound((1,2,3)) => No-op
|
||||
/// NotFound((1,2)) => No-op
|
||||
/// NotFound((2,2, 1)) => Jump
|
||||
NotFound {
|
||||
cursor_id: CursorID,
|
||||
target_pc: BranchOffset,
|
||||
record_reg: usize,
|
||||
num_regs: usize,
|
||||
},
|
||||
/// Apply affinities to a range of registers. Affinities must have the same size of count
|
||||
Affinity {
|
||||
start_reg: usize,
|
||||
count: NonZeroUsize,
|
||||
affinities: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Insn {
|
||||
@@ -808,6 +836,7 @@ impl Insn {
|
||||
match self {
|
||||
Insn::Init { .. } => execute::op_init,
|
||||
Insn::Null { .. } => execute::op_null,
|
||||
Insn::BeginSubrtn { .. } => execute::op_null,
|
||||
Insn::NullRow { .. } => execute::op_null_row,
|
||||
Insn::Add { .. } => execute::op_add,
|
||||
Insn::Subtract { .. } => execute::op_subtract,
|
||||
@@ -915,6 +944,8 @@ impl Insn {
|
||||
Insn::ReadCookie { .. } => execute::op_read_cookie,
|
||||
Insn::OpenEphemeral { .. } | Insn::OpenAutoindex { .. } => execute::op_open_ephemeral,
|
||||
Insn::Once { .. } => execute::op_once,
|
||||
Insn::NotFound { .. } => execute::op_not_found,
|
||||
Insn::Affinity { .. } => execute::op_affinity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user