pub const SORTER_OPEN_TOP_N_REGISTER: u16 = 0x0001;
pub const SORTER_COMPARE_TOP_N_PREFLIGHT: u16 = 0x0001;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
#[allow(clippy::enum_variant_names)]
pub enum Opcode {
Goto = 1,
Gosub = 2,
Return = 3,
InitCoroutine = 4,
EndCoroutine = 5,
Yield = 6,
HaltIfNull = 7,
Halt = 8,
Integer = 9,
Int64 = 10,
Real = 11,
String8 = 12,
String = 13,
BeginSubrtn = 14,
Null = 15,
SoftNull = 16,
Blob = 17,
Variable = 18,
Move = 19,
Copy = 20,
SCopy = 21,
IntCopy = 22,
FkCheck = 23,
ResultRow = 24,
Concat = 25,
Add = 26,
Subtract = 27,
Multiply = 28,
Divide = 29,
Remainder = 30,
CollSeq = 31,
BitAnd = 32,
BitOr = 33,
ShiftLeft = 34,
ShiftRight = 35,
AddImm = 36,
MustBeInt = 37,
RealAffinity = 38,
Cast = 39,
Eq = 40,
Ne = 41,
Lt = 42,
Le = 43,
Gt = 44,
Ge = 45,
ElseEq = 46,
Permutation = 47,
Compare = 48,
Jump = 49,
And = 50,
Or = 51,
IsTrue = 52,
Not = 53,
BitNot = 54,
Once = 55,
If = 56,
IfNot = 57,
IsNull = 58,
IsType = 59,
ZeroOrNull = 60,
NotNull = 61,
IfNullRow = 62,
Offset = 63,
Column = 64,
TypeCheck = 65,
Affinity = 66,
MakeRecord = 67,
Count = 68,
Savepoint = 69,
AutoCommit = 70,
Transaction = 71,
ReadCookie = 72,
SetCookie = 73,
ReopenIdx = 74,
OpenRead = 75,
OpenWrite = 76,
OpenDup = 77,
OpenEphemeral = 78,
OpenAutoindex = 79,
SorterOpen = 80,
SequenceTest = 81,
OpenPseudo = 82,
Close = 83,
ColumnsUsed = 84,
SeekLT = 85,
SeekLE = 86,
SeekGE = 87,
SeekGT = 88,
SeekScan = 89,
SeekHit = 90,
IfNotOpen = 91,
IfNoHope = 92,
NoConflict = 93,
NotFound = 94,
Found = 95,
SeekRowid = 96,
NotExists = 97,
Sequence = 98,
NewRowid = 99,
Insert = 100,
RowCell = 101,
Delete = 102,
ResetCount = 103,
SorterCompare = 104,
SorterData = 105,
RowData = 106,
Rowid = 107,
NullRow = 108,
SeekEnd = 109,
Last = 110,
IfSizeBetween = 111,
SorterSort = 112,
Sort = 113,
Rewind = 114,
IfEmpty = 115,
SorterNext = 116,
Prev = 117,
Next = 118,
IdxInsert = 119,
SorterInsert = 120,
IdxDelete = 121,
DeferredSeek = 122,
IdxRowid = 123,
FinishSeek = 124,
IdxLE = 125,
IdxGT = 126,
IdxLT = 127,
IdxGE = 128,
Destroy = 129,
Clear = 130,
ResetSorter = 131,
CreateBtree = 132,
SqlExec = 133,
ParseSchema = 134,
LoadAnalysis = 135,
DropTable = 136,
DropIndex = 137,
DropTrigger = 138,
IntegrityCk = 139,
RowSetAdd = 140,
RowSetRead = 141,
RowSetTest = 142,
Program = 143,
Param = 144,
FkCounter = 145,
FkIfZero = 146,
MemMax = 147,
IfPos = 148,
OffsetLimit = 149,
IfNotZero = 150,
DecrJumpZero = 151,
AggInverse = 152,
AggStep = 153,
AggStep1 = 154,
AggValue = 155,
AggFinal = 156,
Checkpoint = 157,
JournalMode = 158,
Vacuum = 159,
IncrVacuum = 160,
Expire = 161,
CursorLock = 162,
CursorUnlock = 163,
TableLock = 164,
VBegin = 165,
VCreate = 166,
VDestroy = 167,
VOpen = 168,
VCheck = 169,
VInitIn = 170,
VFilter = 171,
VColumn = 172,
VNext = 173,
VRename = 174,
VUpdate = 175,
Pagecount = 176,
MaxPgcnt = 177,
PureFunc = 178,
Function = 179,
ClrSubtype = 180,
GetSubtype = 181,
SetSubtype = 182,
FilterAdd = 183,
Filter = 184,
Trace = 185,
Init = 186,
CursorHint = 187,
Abortable = 188,
ReleaseReg = 189,
SetSnapshot = 190,
Noop = 191,
LikeConstFast = 192,
CountIndexEqRun = 193,
FusedAppendInsert = 194,
FusedOpenWriteLast = 195,
FusedLiteralResultRow = 196,
ColumnSubstrPrefix = 197,
ColumnOctetLength = 198,
}
impl Opcode {
pub const COUNT: usize = 199;
#[allow(clippy::too_many_lines)]
pub const fn name(self) -> &'static str {
match self {
Self::Goto => "Goto",
Self::Gosub => "Gosub",
Self::Return => "Return",
Self::InitCoroutine => "InitCoroutine",
Self::EndCoroutine => "EndCoroutine",
Self::Yield => "Yield",
Self::HaltIfNull => "HaltIfNull",
Self::Halt => "Halt",
Self::Integer => "Integer",
Self::Int64 => "Int64",
Self::Real => "Real",
Self::String8 => "String8",
Self::String => "String",
Self::BeginSubrtn => "BeginSubrtn",
Self::Null => "Null",
Self::SoftNull => "SoftNull",
Self::Blob => "Blob",
Self::Variable => "Variable",
Self::Move => "Move",
Self::Copy => "Copy",
Self::SCopy => "SCopy",
Self::IntCopy => "IntCopy",
Self::FkCheck => "FkCheck",
Self::ResultRow => "ResultRow",
Self::Concat => "Concat",
Self::Add => "Add",
Self::Subtract => "Subtract",
Self::Multiply => "Multiply",
Self::Divide => "Divide",
Self::Remainder => "Remainder",
Self::CollSeq => "CollSeq",
Self::BitAnd => "BitAnd",
Self::BitOr => "BitOr",
Self::ShiftLeft => "ShiftLeft",
Self::ShiftRight => "ShiftRight",
Self::AddImm => "AddImm",
Self::MustBeInt => "MustBeInt",
Self::RealAffinity => "RealAffinity",
Self::Cast => "Cast",
Self::Eq => "Eq",
Self::Ne => "Ne",
Self::Lt => "Lt",
Self::Le => "Le",
Self::Gt => "Gt",
Self::Ge => "Ge",
Self::ElseEq => "ElseEq",
Self::Permutation => "Permutation",
Self::Compare => "Compare",
Self::Jump => "Jump",
Self::And => "And",
Self::Or => "Or",
Self::IsTrue => "IsTrue",
Self::Not => "Not",
Self::BitNot => "BitNot",
Self::Once => "Once",
Self::If => "If",
Self::IfNot => "IfNot",
Self::IsNull => "IsNull",
Self::IsType => "IsType",
Self::ZeroOrNull => "ZeroOrNull",
Self::NotNull => "NotNull",
Self::IfNullRow => "IfNullRow",
Self::Offset => "Offset",
Self::Column => "Column",
Self::TypeCheck => "TypeCheck",
Self::Affinity => "Affinity",
Self::MakeRecord => "MakeRecord",
Self::Count => "Count",
Self::Savepoint => "Savepoint",
Self::AutoCommit => "AutoCommit",
Self::Transaction => "Transaction",
Self::ReadCookie => "ReadCookie",
Self::SetCookie => "SetCookie",
Self::ReopenIdx => "ReopenIdx",
Self::OpenRead => "OpenRead",
Self::OpenWrite => "OpenWrite",
Self::OpenDup => "OpenDup",
Self::OpenEphemeral => "OpenEphemeral",
Self::OpenAutoindex => "OpenAutoindex",
Self::SorterOpen => "SorterOpen",
Self::SequenceTest => "SequenceTest",
Self::OpenPseudo => "OpenPseudo",
Self::Close => "Close",
Self::ColumnsUsed => "ColumnsUsed",
Self::SeekLT => "SeekLT",
Self::SeekLE => "SeekLE",
Self::SeekGE => "SeekGE",
Self::SeekGT => "SeekGT",
Self::SeekScan => "SeekScan",
Self::SeekHit => "SeekHit",
Self::IfNotOpen => "IfNotOpen",
Self::IfNoHope => "IfNoHope",
Self::NoConflict => "NoConflict",
Self::NotFound => "NotFound",
Self::Found => "Found",
Self::SeekRowid => "SeekRowid",
Self::NotExists => "NotExists",
Self::Sequence => "Sequence",
Self::NewRowid => "NewRowid",
Self::Insert => "Insert",
Self::RowCell => "RowCell",
Self::Delete => "Delete",
Self::ResetCount => "ResetCount",
Self::SorterCompare => "SorterCompare",
Self::SorterData => "SorterData",
Self::RowData => "RowData",
Self::Rowid => "Rowid",
Self::NullRow => "NullRow",
Self::SeekEnd => "SeekEnd",
Self::Last => "Last",
Self::IfSizeBetween => "IfSizeBetween",
Self::SorterSort => "SorterSort",
Self::Sort => "Sort",
Self::Rewind => "Rewind",
Self::IfEmpty => "IfEmpty",
Self::SorterNext => "SorterNext",
Self::Prev => "Prev",
Self::Next => "Next",
Self::IdxInsert => "IdxInsert",
Self::SorterInsert => "SorterInsert",
Self::IdxDelete => "IdxDelete",
Self::DeferredSeek => "DeferredSeek",
Self::IdxRowid => "IdxRowid",
Self::FinishSeek => "FinishSeek",
Self::IdxLE => "IdxLE",
Self::IdxGT => "IdxGT",
Self::IdxLT => "IdxLT",
Self::IdxGE => "IdxGE",
Self::Destroy => "Destroy",
Self::Clear => "Clear",
Self::ResetSorter => "ResetSorter",
Self::CreateBtree => "CreateBtree",
Self::SqlExec => "SqlExec",
Self::ParseSchema => "ParseSchema",
Self::LoadAnalysis => "LoadAnalysis",
Self::DropTable => "DropTable",
Self::DropIndex => "DropIndex",
Self::DropTrigger => "DropTrigger",
Self::IntegrityCk => "IntegrityCk",
Self::RowSetAdd => "RowSetAdd",
Self::RowSetRead => "RowSetRead",
Self::RowSetTest => "RowSetTest",
Self::Program => "Program",
Self::Param => "Param",
Self::FkCounter => "FkCounter",
Self::FkIfZero => "FkIfZero",
Self::MemMax => "MemMax",
Self::IfPos => "IfPos",
Self::OffsetLimit => "OffsetLimit",
Self::IfNotZero => "IfNotZero",
Self::DecrJumpZero => "DecrJumpZero",
Self::AggInverse => "AggInverse",
Self::AggStep => "AggStep",
Self::AggStep1 => "AggStep1",
Self::AggValue => "AggValue",
Self::AggFinal => "AggFinal",
Self::Checkpoint => "Checkpoint",
Self::JournalMode => "JournalMode",
Self::Vacuum => "Vacuum",
Self::IncrVacuum => "IncrVacuum",
Self::Expire => "Expire",
Self::CursorLock => "CursorLock",
Self::CursorUnlock => "CursorUnlock",
Self::TableLock => "TableLock",
Self::VBegin => "VBegin",
Self::VCreate => "VCreate",
Self::VDestroy => "VDestroy",
Self::VOpen => "VOpen",
Self::VCheck => "VCheck",
Self::VInitIn => "VInitIn",
Self::VFilter => "VFilter",
Self::VColumn => "VColumn",
Self::VNext => "VNext",
Self::VRename => "VRename",
Self::VUpdate => "VUpdate",
Self::Pagecount => "Pagecount",
Self::MaxPgcnt => "MaxPgcnt",
Self::PureFunc => "PureFunc",
Self::Function => "Function",
Self::ClrSubtype => "ClrSubtype",
Self::GetSubtype => "GetSubtype",
Self::SetSubtype => "SetSubtype",
Self::FilterAdd => "FilterAdd",
Self::Filter => "Filter",
Self::Trace => "Trace",
Self::Init => "Init",
Self::CursorHint => "CursorHint",
Self::Abortable => "Abortable",
Self::ReleaseReg => "ReleaseReg",
Self::SetSnapshot => "SetSnapshot",
Self::Noop => "Noop",
Self::LikeConstFast => "LikeConstFast",
Self::CountIndexEqRun => "CountIndexEqRun",
Self::FusedAppendInsert => "FusedAppendInsert",
Self::FusedOpenWriteLast => "FusedOpenWriteLast",
Self::FusedLiteralResultRow => "FusedLiteralResultRow",
Self::ColumnSubstrPrefix => "ColumnSubstrPrefix",
Self::ColumnOctetLength => "ColumnOctetLength",
}
}
#[allow(clippy::too_many_lines)]
pub const fn from_byte(byte: u8) -> Option<Self> {
if byte == 0 || byte as usize >= Self::COUNT {
return None;
}
match byte {
1 => Some(Self::Goto),
2 => Some(Self::Gosub),
3 => Some(Self::Return),
4 => Some(Self::InitCoroutine),
5 => Some(Self::EndCoroutine),
6 => Some(Self::Yield),
7 => Some(Self::HaltIfNull),
8 => Some(Self::Halt),
9 => Some(Self::Integer),
10 => Some(Self::Int64),
11 => Some(Self::Real),
12 => Some(Self::String8),
13 => Some(Self::String),
14 => Some(Self::BeginSubrtn),
15 => Some(Self::Null),
16 => Some(Self::SoftNull),
17 => Some(Self::Blob),
18 => Some(Self::Variable),
19 => Some(Self::Move),
20 => Some(Self::Copy),
21 => Some(Self::SCopy),
22 => Some(Self::IntCopy),
23 => Some(Self::FkCheck),
24 => Some(Self::ResultRow),
25 => Some(Self::Concat),
26 => Some(Self::Add),
27 => Some(Self::Subtract),
28 => Some(Self::Multiply),
29 => Some(Self::Divide),
30 => Some(Self::Remainder),
31 => Some(Self::CollSeq),
32 => Some(Self::BitAnd),
33 => Some(Self::BitOr),
34 => Some(Self::ShiftLeft),
35 => Some(Self::ShiftRight),
36 => Some(Self::AddImm),
37 => Some(Self::MustBeInt),
38 => Some(Self::RealAffinity),
39 => Some(Self::Cast),
40 => Some(Self::Eq),
41 => Some(Self::Ne),
42 => Some(Self::Lt),
43 => Some(Self::Le),
44 => Some(Self::Gt),
45 => Some(Self::Ge),
46 => Some(Self::ElseEq),
47 => Some(Self::Permutation),
48 => Some(Self::Compare),
49 => Some(Self::Jump),
50 => Some(Self::And),
51 => Some(Self::Or),
52 => Some(Self::IsTrue),
53 => Some(Self::Not),
54 => Some(Self::BitNot),
55 => Some(Self::Once),
56 => Some(Self::If),
57 => Some(Self::IfNot),
58 => Some(Self::IsNull),
59 => Some(Self::IsType),
60 => Some(Self::ZeroOrNull),
61 => Some(Self::NotNull),
62 => Some(Self::IfNullRow),
63 => Some(Self::Offset),
64 => Some(Self::Column),
65 => Some(Self::TypeCheck),
66 => Some(Self::Affinity),
67 => Some(Self::MakeRecord),
68 => Some(Self::Count),
69 => Some(Self::Savepoint),
70 => Some(Self::AutoCommit),
71 => Some(Self::Transaction),
72 => Some(Self::ReadCookie),
73 => Some(Self::SetCookie),
74 => Some(Self::ReopenIdx),
75 => Some(Self::OpenRead),
76 => Some(Self::OpenWrite),
77 => Some(Self::OpenDup),
78 => Some(Self::OpenEphemeral),
79 => Some(Self::OpenAutoindex),
80 => Some(Self::SorterOpen),
81 => Some(Self::SequenceTest),
82 => Some(Self::OpenPseudo),
83 => Some(Self::Close),
84 => Some(Self::ColumnsUsed),
85 => Some(Self::SeekLT),
86 => Some(Self::SeekLE),
87 => Some(Self::SeekGE),
88 => Some(Self::SeekGT),
89 => Some(Self::SeekScan),
90 => Some(Self::SeekHit),
91 => Some(Self::IfNotOpen),
92 => Some(Self::IfNoHope),
93 => Some(Self::NoConflict),
94 => Some(Self::NotFound),
95 => Some(Self::Found),
96 => Some(Self::SeekRowid),
97 => Some(Self::NotExists),
98 => Some(Self::Sequence),
99 => Some(Self::NewRowid),
100 => Some(Self::Insert),
101 => Some(Self::RowCell),
102 => Some(Self::Delete),
103 => Some(Self::ResetCount),
104 => Some(Self::SorterCompare),
105 => Some(Self::SorterData),
106 => Some(Self::RowData),
107 => Some(Self::Rowid),
108 => Some(Self::NullRow),
109 => Some(Self::SeekEnd),
110 => Some(Self::Last),
111 => Some(Self::IfSizeBetween),
112 => Some(Self::SorterSort),
113 => Some(Self::Sort),
114 => Some(Self::Rewind),
115 => Some(Self::IfEmpty),
116 => Some(Self::SorterNext),
117 => Some(Self::Prev),
118 => Some(Self::Next),
119 => Some(Self::IdxInsert),
120 => Some(Self::SorterInsert),
121 => Some(Self::IdxDelete),
122 => Some(Self::DeferredSeek),
123 => Some(Self::IdxRowid),
124 => Some(Self::FinishSeek),
125 => Some(Self::IdxLE),
126 => Some(Self::IdxGT),
127 => Some(Self::IdxLT),
128 => Some(Self::IdxGE),
129 => Some(Self::Destroy),
130 => Some(Self::Clear),
131 => Some(Self::ResetSorter),
132 => Some(Self::CreateBtree),
133 => Some(Self::SqlExec),
134 => Some(Self::ParseSchema),
135 => Some(Self::LoadAnalysis),
136 => Some(Self::DropTable),
137 => Some(Self::DropIndex),
138 => Some(Self::DropTrigger),
139 => Some(Self::IntegrityCk),
140 => Some(Self::RowSetAdd),
141 => Some(Self::RowSetRead),
142 => Some(Self::RowSetTest),
143 => Some(Self::Program),
144 => Some(Self::Param),
145 => Some(Self::FkCounter),
146 => Some(Self::FkIfZero),
147 => Some(Self::MemMax),
148 => Some(Self::IfPos),
149 => Some(Self::OffsetLimit),
150 => Some(Self::IfNotZero),
151 => Some(Self::DecrJumpZero),
152 => Some(Self::AggInverse),
153 => Some(Self::AggStep),
154 => Some(Self::AggStep1),
155 => Some(Self::AggValue),
156 => Some(Self::AggFinal),
157 => Some(Self::Checkpoint),
158 => Some(Self::JournalMode),
159 => Some(Self::Vacuum),
160 => Some(Self::IncrVacuum),
161 => Some(Self::Expire),
162 => Some(Self::CursorLock),
163 => Some(Self::CursorUnlock),
164 => Some(Self::TableLock),
165 => Some(Self::VBegin),
166 => Some(Self::VCreate),
167 => Some(Self::VDestroy),
168 => Some(Self::VOpen),
169 => Some(Self::VCheck),
170 => Some(Self::VInitIn),
171 => Some(Self::VFilter),
172 => Some(Self::VColumn),
173 => Some(Self::VNext),
174 => Some(Self::VRename),
175 => Some(Self::VUpdate),
176 => Some(Self::Pagecount),
177 => Some(Self::MaxPgcnt),
178 => Some(Self::PureFunc),
179 => Some(Self::Function),
180 => Some(Self::ClrSubtype),
181 => Some(Self::GetSubtype),
182 => Some(Self::SetSubtype),
183 => Some(Self::FilterAdd),
184 => Some(Self::Filter),
185 => Some(Self::Trace),
186 => Some(Self::Init),
187 => Some(Self::CursorHint),
188 => Some(Self::Abortable),
189 => Some(Self::ReleaseReg),
190 => Some(Self::SetSnapshot),
191 => Some(Self::Noop),
192 => Some(Self::LikeConstFast),
193 => Some(Self::CountIndexEqRun),
194 => Some(Self::FusedAppendInsert),
195 => Some(Self::FusedOpenWriteLast),
196 => Some(Self::FusedLiteralResultRow),
197 => Some(Self::ColumnSubstrPrefix),
198 => Some(Self::ColumnOctetLength),
_ => None,
}
}
pub const fn is_jump(self) -> bool {
matches!(
self,
Self::Goto
| Self::Gosub
| Self::InitCoroutine
| Self::Yield
| Self::HaltIfNull
| Self::Once
| Self::If
| Self::IfNot
| Self::IsNull
| Self::IsType
| Self::NotNull
| Self::IfNullRow
| Self::Jump
| Self::Eq
| Self::Ne
| Self::Lt
| Self::Le
| Self::Gt
| Self::Ge
| Self::ElseEq
| Self::SeekLT
| Self::SeekLE
| Self::SeekGE
| Self::SeekGT
| Self::SeekRowid
| Self::NotExists
| Self::IfNotOpen
| Self::IfNoHope
| Self::NoConflict
| Self::NotFound
| Self::Found
| Self::Last
| Self::Rewind
| Self::IfEmpty
| Self::IfSizeBetween
| Self::Next
| Self::Prev
| Self::SorterNext
| Self::SorterSort
| Self::Sort
| Self::IdxLE
| Self::IdxGT
| Self::IdxLT
| Self::IdxGE
| Self::RowSetRead
| Self::RowSetTest
| Self::Program
| Self::FkIfZero
| Self::IfPos
| Self::IfNotZero
| Self::DecrJumpZero
| Self::IncrVacuum
| Self::VFilter
| Self::VNext
| Self::Filter
| Self::Init
)
}
}
impl std::fmt::Display for Opcode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VdbeOp {
pub opcode: Opcode,
pub p1: i32,
pub p2: i32,
pub p3: i32,
pub p4: P4,
pub p5: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexCursorMeta {
pub cursor_id: i32,
pub column_indices: Vec<usize>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum P4 {
None,
Int(i32),
Int64(i64),
Real(f64),
Str(String),
Blob(Vec<u8>),
Collation(String),
FuncName(String),
FuncNameCollated(String, String),
Table(String),
Index(String),
Affinity(String),
PrecomputedHeader(crate::record::PrecomputedRecordHeader),
TimeTravelCommitSeq(u64),
TimeTravelTimestamp(String),
}
use fsqlite_error::{FrankenError, Result};
use smallvec::SmallVec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Label(u32);
#[derive(Debug)]
enum LabelState {
Unresolved(Vec<usize>),
Resolved(i32),
}
#[derive(Debug)]
pub struct RegisterAllocator {
next_reg: i32,
temp_pool: Vec<i32>,
}
impl RegisterAllocator {
#[must_use]
pub fn new() -> Self {
Self {
next_reg: 1,
temp_pool: Vec::new(),
}
}
pub fn alloc_reg(&mut self) -> i32 {
let reg = self.next_reg;
self.next_reg += 1;
reg
}
pub fn alloc_regs(&mut self, n: i32) -> i32 {
let first = self.next_reg;
self.next_reg += n;
first
}
pub fn alloc_temp(&mut self) -> i32 {
self.temp_pool.pop().unwrap_or_else(|| {
let reg = self.next_reg;
self.next_reg += 1;
reg
})
}
pub fn free_temp(&mut self, reg: i32) {
self.temp_pool.push(reg);
}
#[must_use]
pub fn count(&self) -> i32 {
self.next_reg - 1
}
}
impl Default for RegisterAllocator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ProgramBuilder {
ops: SmallVec<[VdbeOp; 64]>,
labels: Vec<LabelState>,
regs: RegisterAllocator,
}
impl ProgramBuilder {
#[must_use]
pub fn new() -> Self {
Self {
ops: SmallVec::new(),
labels: Vec::new(),
regs: RegisterAllocator::new(),
}
}
pub fn emit(&mut self, op: VdbeOp) -> usize {
let addr = self.ops.len();
self.ops.push(op);
addr
}
pub fn emit_op(&mut self, opcode: Opcode, p1: i32, p2: i32, p3: i32, p4: P4, p5: u16) -> usize {
self.emit(VdbeOp {
opcode,
p1,
p2,
p3,
p4,
p5,
})
}
#[must_use]
pub fn current_addr(&self) -> usize {
self.ops.len()
}
#[must_use]
pub fn op_at(&self, addr: usize) -> Option<&VdbeOp> {
self.ops.get(addr)
}
#[must_use]
pub fn op_at_mut(&mut self, addr: usize) -> Option<&mut VdbeOp> {
self.ops.get_mut(addr)
}
#[must_use]
pub fn emit_label(&mut self) -> Label {
let id = u32::try_from(self.labels.len()).expect("too many labels");
self.labels.push(LabelState::Unresolved(Vec::new()));
Label(id)
}
pub fn emit_jump_to_label(
&mut self,
opcode: Opcode,
p1: i32,
p3: i32,
label: Label,
p4: P4,
p5: u16,
) -> usize {
let addr = self.emit(VdbeOp {
opcode,
p1,
p2: -1, p3,
p4,
p5,
});
let state = self
.labels
.get_mut(usize::try_from(label.0).expect("label fits usize"))
.expect("label must exist");
match state {
LabelState::Unresolved(refs) => refs.push(addr),
LabelState::Resolved(target) => {
self.ops[addr].p2 = *target;
}
}
addr
}
pub fn resolve_label(&mut self, label: Label) {
let addr = i32::try_from(self.current_addr()).expect("program too large");
self.resolve_label_to(label, addr);
}
pub fn resolve_label_to(&mut self, label: Label, address: i32) {
let idx = usize::try_from(label.0).expect("label fits usize");
let state = self.labels.get_mut(idx).expect("label must exist");
match state {
LabelState::Unresolved(refs) => {
for &ref_addr in refs.iter() {
self.ops[ref_addr].p2 = address;
}
*state = LabelState::Resolved(address);
}
LabelState::Resolved(_) => {
*state = LabelState::Resolved(address);
}
}
}
pub fn alloc_reg(&mut self) -> i32 {
self.regs.alloc_reg()
}
pub fn alloc_regs(&mut self, n: i32) -> i32 {
self.regs.alloc_regs(n)
}
pub fn alloc_temp(&mut self) -> i32 {
self.regs.alloc_temp()
}
pub fn free_temp(&mut self, reg: i32) {
self.regs.free_temp(reg);
}
#[must_use]
pub fn register_count(&self) -> i32 {
self.regs.count()
}
pub fn apply_fuse_literal_result_row(&mut self) -> usize {
let mut jump_targets: std::collections::HashSet<i32> = std::collections::HashSet::new();
for op in &self.ops {
if op.opcode.is_jump() {
jump_targets.insert(op.p2);
}
}
let mut fused = 0usize;
let len = self.ops.len();
let mut i = 0;
while i + 1 < len {
let is_int = matches!(self.ops[i].opcode, Opcode::Integer)
&& self.ops[i].p3 == 0
&& self.ops[i].p5 == 0
&& matches!(self.ops[i].p4, P4::None);
let is_row = matches!(self.ops[i + 1].opcode, Opcode::ResultRow)
&& self.ops[i + 1].p2 == 1
&& self.ops[i + 1].p3 == 0
&& self.ops[i + 1].p5 == 0
&& matches!(self.ops[i + 1].p4, P4::None);
let same_reg = is_int && is_row && self.ops[i].p2 == self.ops[i + 1].p1;
let row_addr = i32::try_from(i + 1).ok();
let row_is_target = row_addr.is_some_and(|a| jump_targets.contains(&a));
if same_reg && !row_is_target {
let lit = self.ops[i].p1;
let reg = self.ops[i].p2;
self.ops[i] = VdbeOp {
opcode: Opcode::FusedLiteralResultRow,
p1: lit,
p2: reg,
p3: 0,
p4: P4::None,
p5: 0,
};
self.ops[i + 1] = VdbeOp {
opcode: Opcode::Noop,
p1: 0,
p2: 0,
p3: 0,
p4: P4::None,
p5: 0,
};
fused += 1;
i += 2;
} else {
i += 1;
}
}
fused
}
pub fn finish(self) -> Result<VdbeProgram> {
for (i, state) in self.labels.iter().enumerate() {
if let LabelState::Unresolved(refs) = state
&& !refs.is_empty()
{
return Err(FrankenError::Internal(format!(
"unresolved label {i} referenced by {} instruction(s)",
refs.len()
)));
}
}
Ok(VdbeProgram {
ops: self.ops,
register_count: self.regs.count(),
})
}
}
impl Default for ProgramBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VdbeProgram {
ops: SmallVec<[VdbeOp; 64]>,
register_count: i32,
}
impl VdbeProgram {
#[must_use]
pub fn ops(&self) -> &[VdbeOp] {
&self.ops
}
#[must_use]
pub fn len(&self) -> usize {
self.ops.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.ops.is_empty()
}
#[must_use]
pub fn register_count(&self) -> i32 {
self.register_count
}
#[must_use]
pub fn get(&self, pc: usize) -> Option<&VdbeOp> {
self.ops.get(pc)
}
#[must_use]
pub fn disassemble(&self) -> String {
use std::fmt::Write;
let mut out = std::string::String::with_capacity(self.ops.len() * 60);
out.push_str("addr opcode p1 p2 p3 p4 p5\n");
out.push_str("---- --------------- ---- ---- ---- ----------------- --\n");
for (addr, op) in self.ops.iter().enumerate() {
let p4_str = match &op.p4 {
P4::None => String::new(),
P4::Int(v) => format!("(int){v}"),
P4::Int64(v) => format!("(i64){v}"),
P4::Real(v) => format!("(real){v}"),
P4::Str(s) => format!("(str){s}"),
P4::Blob(b) => format!("(blob)[{}B]", b.len()),
P4::Collation(c) => format!("(coll){c}"),
P4::FuncName(f) => format!("(func){f}"),
P4::FuncNameCollated(f, c) => format!("(func){f} coll={c}"),
P4::Table(t) => format!("(tbl){t}"),
P4::Index(i) => format!("(idx){i}"),
P4::Affinity(a) => format!("(aff){a}"),
P4::PrecomputedHeader(header) => format!("(hdr)[{}B]", header.template.len()),
P4::TimeTravelCommitSeq(seq) => format!("(tt-seq){seq}"),
P4::TimeTravelTimestamp(ts) => format!("(tt-ts){ts}"),
};
writeln!(
&mut out,
"{addr:<4} {:<15} {:<4} {:<4} {:<4} {:<17} {:<2}",
op.opcode.name(),
op.p1,
op.p2,
op.p3,
p4_str,
op.p5,
)
.expect("write to string");
}
out
}
}
#[cfg(test)]
#[allow(clippy::approx_constant)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn opcode_count() {
assert_eq!(Opcode::COUNT, 199);
assert_eq!(Opcode::COUNT - 1, 198);
}
#[test]
fn opcode_name_roundtrip() {
assert_eq!(Opcode::Goto.name(), "Goto");
assert_eq!(Opcode::Halt.name(), "Halt");
assert_eq!(Opcode::Insert.name(), "Insert");
assert_eq!(Opcode::Delete.name(), "Delete");
assert_eq!(Opcode::ResultRow.name(), "ResultRow");
assert_eq!(Opcode::Noop.name(), "Noop");
}
#[test]
fn opcode_from_byte() {
assert_eq!(Opcode::from_byte(0), None);
assert_eq!(Opcode::from_byte(1), Some(Opcode::Goto));
assert_eq!(Opcode::from_byte(8), Some(Opcode::Halt));
assert_eq!(Opcode::from_byte(190), Some(Opcode::SetSnapshot));
assert_eq!(Opcode::from_byte(191), Some(Opcode::Noop));
assert_eq!(Opcode::from_byte(192), Some(Opcode::LikeConstFast));
assert_eq!(Opcode::from_byte(196), Some(Opcode::FusedLiteralResultRow));
assert_eq!(Opcode::from_byte(197), Some(Opcode::ColumnSubstrPrefix));
assert_eq!(Opcode::from_byte(198), Some(Opcode::ColumnOctetLength));
assert_eq!(Opcode::from_byte(199), None);
assert_eq!(Opcode::from_byte(255), None);
}
#[test]
fn opcode_from_byte_exhaustive() {
for i in 1..Opcode::COUNT as u8 {
assert!(
Opcode::from_byte(i).is_some(),
"from_byte({i}) returned None"
);
}
}
#[test]
fn test_opcode_distinct_u8_values() {
let mut encoded = HashSet::new();
for byte in 1..Opcode::COUNT as u8 {
let opcode = Opcode::from_byte(byte).expect("opcode byte must decode");
let inserted = encoded.insert(opcode as u8);
assert!(inserted, "duplicate opcode byte value for {:?}", opcode);
}
assert_eq!(
encoded.len(),
Opcode::COUNT - 1,
"every opcode must map to a unique byte"
);
}
#[test]
fn opcode_display() {
assert_eq!(Opcode::Goto.to_string(), "Goto");
assert_eq!(Opcode::Init.to_string(), "Init");
}
#[test]
fn opcode_is_jump() {
assert!(Opcode::Goto.is_jump());
assert!(Opcode::If.is_jump());
assert!(Opcode::IfNot.is_jump());
assert!(Opcode::Eq.is_jump());
assert!(Opcode::Next.is_jump());
assert!(Opcode::Rewind.is_jump());
assert!(Opcode::Init.is_jump());
assert!(!Opcode::Integer.is_jump());
assert!(!Opcode::Add.is_jump());
assert!(!Opcode::Insert.is_jump());
assert!(!Opcode::Noop.is_jump());
assert!(!Opcode::ResultRow.is_jump());
}
#[test]
fn vdbe_op_basic() {
let op = VdbeOp {
opcode: Opcode::Integer,
p1: 42,
p2: 1,
p3: 0,
p4: P4::None,
p5: 0,
};
assert_eq!(op.opcode, Opcode::Integer);
assert_eq!(op.p1, 42);
}
#[test]
fn p4_variants() {
let p4 = P4::Int(42);
assert_eq!(p4, P4::Int(42));
let p4 = P4::Str("hello".to_owned());
assert_eq!(p4, P4::Str("hello".to_owned()));
let p4 = P4::Real(3.14);
assert_eq!(p4, P4::Real(3.14));
}
}