Skip to main content

fsqlite_types/
opcode.rs

1/// Read the top-N bound for `SorterOpen` from register P3 instead of treating
2/// P3 as an immediate integer.
3pub const SORTER_OPEN_TOP_N_REGISTER: u16 = 0x0001;
4
5/// Make `SorterCompare` treat P3 as a candidate sort-key record and jump to P2
6/// when a bounded sorter would reject that candidate.
7///
8/// In this mode P3 is consumed: after either the P2 jump or fallthrough, the
9/// register is dead and its contents are undefined. Bytecode must rewrite P3
10/// before any later read.
11pub const SORTER_COMPARE_TOP_N_PREFLIGHT: u16 = 0x0001;
12
13/// VDBE (Virtual Database Engine) opcodes.
14///
15/// These correspond 1:1 to the upstream SQLite VDBE opcode set. Each opcode
16/// represents a single operation in the bytecode program that the VDBE
17/// executes. Opcodes are numbered sequentially; the specific numeric values
18/// match C SQLite for debugging/comparison purposes.
19///
20/// Reference: canonical upstream SQLite opcode definitions.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[repr(u8)]
23#[allow(clippy::enum_variant_names)]
24pub enum Opcode {
25    // === Control Flow ===
26    /// Jump to address P2.
27    Goto = 1,
28    /// Push return address, jump to P2.
29    Gosub = 2,
30    /// Pop return address, jump to it.
31    Return = 3,
32    /// Initialize coroutine. P1=coroutine reg, P2=jump on first entry.
33    InitCoroutine = 4,
34    /// End coroutine, jump to return address.
35    EndCoroutine = 5,
36    /// Yield control to/from coroutine.
37    Yield = 6,
38    /// Halt if register P3 is NULL.
39    HaltIfNull = 7,
40    /// Halt execution (with optional error).
41    Halt = 8,
42
43    // === Constants & Values ===
44    /// Set register P2 to integer value P1.
45    Integer = 9,
46    /// Set register P2 to 64-bit integer from P4.
47    Int64 = 10,
48    /// Set register P2 to real value from P4.
49    Real = 11,
50    /// Set register P2 to string P4 (zero-terminated).
51    String8 = 12,
52    /// Set register P2 to string of length P1 from P4.
53    String = 13,
54    /// Begin subroutine / set register P2 to NULL.
55    BeginSubrtn = 14,
56    /// Set registers P2..P2+P3-1 to NULL.
57    Null = 15,
58    /// Set register to soft NULL (for optimization).
59    SoftNull = 16,
60    /// Set register P2 to blob of length P1 from P4.
61    Blob = 17,
62    /// Set register P2 to the value of variable/parameter P1.
63    Variable = 18,
64
65    // === Register Operations ===
66    /// Move P3 registers from P1 to P2.
67    Move = 19,
68    /// Copy register P1 to P2 (and optionally more).
69    Copy = 20,
70    /// Shallow copy register P1 to P2.
71    SCopy = 21,
72    /// Copy integer value from P1 to P2.
73    IntCopy = 22,
74
75    // === Foreign Key ===
76    /// Check foreign key constraints.
77    FkCheck = 23,
78
79    // === Result ===
80    /// Output a row of P2 registers starting at P1.
81    ResultRow = 24,
82
83    // === Arithmetic & String ===
84    /// Concatenate P1 and P2, store in P3.
85    Concat = 25,
86    /// P3 = P2 + P1.
87    Add = 26,
88    /// P3 = P2 - P1.
89    Subtract = 27,
90    /// P3 = P2 * P1.
91    Multiply = 28,
92    /// P3 = P2 / P1.
93    Divide = 29,
94    /// P3 = P2 % P1.
95    Remainder = 30,
96
97    // === Collation ===
98    /// Set collation sequence for comparison.
99    CollSeq = 31,
100
101    // === Bitwise ===
102    /// P3 = P1 & P2.
103    BitAnd = 32,
104    /// P3 = P1 | P2.
105    BitOr = 33,
106    /// P3 = P2 << P1.
107    ShiftLeft = 34,
108    /// P3 = P2 >> P1.
109    ShiftRight = 35,
110
111    // === Type Conversion ===
112    /// Add integer P2 to register P1.
113    AddImm = 36,
114    /// Fail if P1 is not an integer; optionally jump to P2.
115    MustBeInt = 37,
116    /// If P1 is integer, convert to real in-place.
117    RealAffinity = 38,
118    /// Cast register P1 to type P2.
119    Cast = 39,
120
121    // === Comparison ===
122    /// Jump to P2 if P1 == P3.
123    Eq = 40,
124    /// Jump to P2 if P1 != P3.
125    Ne = 41,
126    /// Jump to P2 if P3 < P1.
127    Lt = 42,
128    /// Jump to P2 if P3 <= P1.
129    Le = 43,
130    /// Jump to P2 if P3 > P1.
131    Gt = 44,
132    /// Jump to P2 if P3 >= P1.
133    Ge = 45,
134    /// Jump if the previous comparison was Eq (for multi-column indexes).
135    ElseEq = 46,
136
137    // === Permutation & Compare ===
138    /// Set up permutation for subsequent Compare.
139    Permutation = 47,
140    /// Compare P1..P1+P3-1 with P2..P2+P3-1.
141    Compare = 48,
142
143    // === Branching ===
144    /// Jump to one of P1, P2, or P3 based on comparison result.
145    Jump = 49,
146    /// P3 = P1 AND P2 (three-valued logic).
147    And = 50,
148    /// P3 = P1 OR P2 (three-valued logic).
149    Or = 51,
150    /// Apply IS TRUE test.
151    IsTrue = 52,
152    /// P2 = NOT P1.
153    Not = 53,
154    /// P2 = ~P1 (bitwise not).
155    BitNot = 54,
156    /// Jump to P2 on first execution only.
157    Once = 55,
158    /// Jump to P2 if P1 is true (non-zero and non-NULL).
159    If = 56,
160    /// Jump to P2 if P1 is false (zero or NULL).
161    IfNot = 57,
162    /// Jump to P2 if P1 is NULL.
163    IsNull = 58,
164    /// Type check against P5 type mask; jump to P2 on mismatch.
165    IsType = 59,
166    /// P2 = 0 if any of P1, P2, P3 is NULL.
167    ZeroOrNull = 60,
168    /// Jump to P2 if P1 is not NULL.
169    NotNull = 61,
170    /// Jump to P2 if the current row of cursor P1 is NULL.
171    IfNullRow = 62,
172
173    // === Column Access ===
174    /// Extract byte offset of cursor.
175    Offset = 63,
176    /// Extract column P2 from cursor P1 into register P3.
177    Column = 64,
178    /// Type-check columns against declared types.
179    TypeCheck = 65,
180    /// Apply type affinity to P2 registers starting at P1.
181    Affinity = 66,
182
183    // === Record Building ===
184    /// Build a record from P1..P1+P2-1 registers into P3.
185    MakeRecord = 67,
186
187    // === Counting ===
188    /// Store the number of rows in cursor P1 into register P2.
189    Count = 68,
190
191    // === Transaction Control ===
192    /// Begin, release, or rollback a savepoint.
193    Savepoint = 69,
194    /// Set or clear auto-commit mode.
195    AutoCommit = 70,
196    /// Begin a transaction on database P1.
197    Transaction = 71,
198
199    // === Cookie Access ===
200    /// Read database cookie P3 from database P1 into register P2.
201    ReadCookie = 72,
202    /// Write P3 to database cookie P2 of database P1.
203    SetCookie = 73,
204
205    // === Cursor Operations ===
206    /// Reopen an index cursor (P1) if it's on a different root page.
207    ReopenIdx = 74,
208    /// Open a read cursor on table/index P2 in database P3.
209    OpenRead = 75,
210    /// Open a write cursor on table/index P2 in database P3.
211    OpenWrite = 76,
212    /// Open cursor P1 as a duplicate of cursor P2.
213    OpenDup = 77,
214    /// Open an ephemeral (temporary) table cursor.
215    OpenEphemeral = 78,
216    /// Open an auto-index ephemeral cursor.
217    OpenAutoindex = 79,
218    /// Open a sorter cursor.
219    SorterOpen = 80,
220    /// Test if sequence number has been used.
221    SequenceTest = 81,
222    /// Open a pseudo-table cursor (reads from a register).
223    OpenPseudo = 82,
224    /// Close cursor P1.
225    Close = 83,
226    /// Set the columns-used mask for cursor P1.
227    ColumnsUsed = 84,
228
229    // === Seek Operations ===
230    /// Seek cursor P1 to the largest entry less than P3.
231    SeekLT = 85,
232    /// Seek cursor P1 to the largest entry <= P3.
233    SeekLE = 86,
234    /// Seek cursor P1 to the smallest entry >= P3.
235    SeekGE = 87,
236    /// Seek cursor P1 to the smallest entry greater than P3.
237    SeekGT = 88,
238    /// Optimized seek-scan for small result sets.
239    SeekScan = 89,
240    /// Mark seek hit range for covering index optimization.
241    SeekHit = 90,
242    /// Jump to P2 if cursor P1 is not open.
243    IfNotOpen = 91,
244
245    // === Index Lookup ===
246    /// Like NotFound but with Bloom filter check.
247    IfNoHope = 92,
248    /// Jump to P2 if key P3 is NOT found (no conflict).
249    NoConflict = 93,
250    /// Jump to P2 if key P3 is NOT found in cursor P1.
251    NotFound = 94,
252    /// Jump to P2 if key P3 IS found in cursor P1.
253    Found = 95,
254
255    // === Rowid Seek ===
256    /// Seek cursor P1 to rowid P3; jump to P2 if not found.
257    SeekRowid = 96,
258    /// Jump to P2 if rowid P3 does NOT exist in cursor P1.
259    NotExists = 97,
260
261    // === Sequence & Rowid ===
262    /// Store next sequence value for cursor P1 into register P2.
263    Sequence = 98,
264    /// Generate a new unique rowid for cursor P1.
265    NewRowid = 99,
266
267    // === Insert & Delete ===
268    /// Insert record from P2 with rowid P3 into cursor P1.
269    Insert = 100,
270    /// Copy a cell directly from one cursor to another.
271    RowCell = 101,
272    /// Delete the current row of cursor P1.
273    Delete = 102,
274    /// Reset the change counter.
275    ResetCount = 103,
276
277    // === Sorter Operations ===
278    /// Compare a sorter key.
279    ///
280    /// With `SORTER_COMPARE_TOP_N_PREFLIGHT`, P3 is a packed candidate-key
281    /// record and execution jumps to P2 when the bounded sorter is full and
282    /// the candidate cannot displace its current worst row.
283    /// The preflight form consumes P3; it is dead and undefined after either
284    /// the jump or fallthrough and must be rewritten before any later read.
285    SorterCompare = 104,
286    /// Read data from the sorter.
287    SorterData = 105,
288
289    // === Row Data ===
290    /// Copy the complete row data of cursor P1 into register P2.
291    RowData = 106,
292    /// Store the rowid of cursor P1 into register P2.
293    Rowid = 107,
294    /// Set cursor P1 to a NULL row.
295    NullRow = 108,
296
297    // === Cursor Navigation ===
298    /// Seek to end of table (no-op for reading, positions for append).
299    SeekEnd = 109,
300    /// Move cursor P1 to the last entry; jump to P2 if empty.
301    Last = 110,
302    /// Jump to P2 if table size is between P3 and P4.
303    IfSizeBetween = 111,
304    /// Sort (alias for SorterSort in some contexts).
305    SorterSort = 112,
306    /// Sort cursor P1.
307    Sort = 113,
308    /// Rewind cursor P1 to the first entry; jump to P2 if empty.
309    Rewind = 114,
310    /// Jump to P2 if cursor P1's table is empty.
311    IfEmpty = 115,
312
313    // === Iteration ===
314    /// Advance sorter to next entry.
315    SorterNext = 116,
316    /// Move cursor P1 to the previous entry; jump to P2 if done.
317    Prev = 117,
318    /// Move cursor P1 to the next entry; jump to P2 if done.
319    Next = 118,
320
321    // === Index Insert/Delete ===
322    /// Insert record P2 into index cursor P1.
323    IdxInsert = 119,
324    /// Insert into sorter.
325    SorterInsert = 120,
326    /// Delete from index cursor P1.
327    IdxDelete = 121,
328
329    // === Deferred Seek ===
330    /// Defer a seek on cursor P1 using the rowid from index cursor P2.
331    DeferredSeek = 122,
332    /// Extract rowid from index entry of cursor P1.
333    IdxRowid = 123,
334    /// Complete a previously deferred seek.
335    FinishSeek = 124,
336
337    // === Index Comparison ===
338    /// Jump to P2 if index key of P1 <= key.
339    IdxLE = 125,
340    /// Jump to P2 if index key of P1 > key.
341    IdxGT = 126,
342    /// Jump to P2 if index key of P1 < key.
343    IdxLT = 127,
344    /// Jump to P2 if index key of P1 >= key.
345    IdxGE = 128,
346
347    // === DDL Operations ===
348    /// Destroy (drop) a B-tree rooted at page P1.
349    Destroy = 129,
350    /// Clear (delete all rows from) a table or index.
351    Clear = 130,
352    /// Reset a sorter cursor.
353    ResetSorter = 131,
354    /// Allocate a new B-tree, store root page number in P2.
355    CreateBtree = 132,
356
357    // === Schema Operations ===
358    /// Execute an SQL statement stored in P4.
359    SqlExec = 133,
360    /// Parse the schema for database P1.
361    ParseSchema = 134,
362    /// Load analysis data for database P1.
363    LoadAnalysis = 135,
364    /// Drop a table.
365    DropTable = 136,
366    /// Drop an index.
367    DropIndex = 137,
368    /// Drop a trigger.
369    DropTrigger = 138,
370
371    // === Integrity Check ===
372    /// Run integrity check on database P1.
373    IntegrityCk = 139,
374
375    // === RowSet Operations ===
376    /// Add integer P2 to rowset P1.
377    RowSetAdd = 140,
378    /// Read next value from rowset P1 into P3; jump to P2 when empty.
379    RowSetRead = 141,
380    /// Test if P3 exists in rowset P1; jump to P2 if found.
381    RowSetTest = 142,
382
383    // === Trigger/Program ===
384    /// Call a trigger sub-program.
385    Program = 143,
386    /// Copy trigger parameter into register P2.
387    Param = 144,
388
389    // === FK Counters ===
390    /// Increment or decrement FK counter.
391    FkCounter = 145,
392    /// Jump to P2 if FK counter is zero.
393    FkIfZero = 146,
394
395    // === Memory/Counter ===
396    /// Set register P2 to max of P2 and register P1.
397    MemMax = 147,
398
399    // === Conditional Jumps ===
400    /// Jump to P2 if register P1 > 0; decrement by P3.
401    IfPos = 148,
402    /// Compute offset limit.
403    OffsetLimit = 149,
404    /// Jump to P2 if register P1 is not zero.
405    IfNotZero = 150,
406    /// Decrement P1, jump to P2 if result is zero.
407    DecrJumpZero = 151,
408
409    // === Aggregate Functions ===
410    /// Invoke aggregate inverse function.
411    AggInverse = 152,
412    /// Invoke aggregate step function.
413    AggStep = 153,
414    /// Step variant with different init semantics.
415    AggStep1 = 154,
416    /// Extract aggregate intermediate value.
417    AggValue = 155,
418    /// Finalize aggregate function.
419    AggFinal = 156,
420
421    // === WAL & Journal ===
422    /// Checkpoint the WAL for database P1.
423    Checkpoint = 157,
424    /// Set journal mode for database P1.
425    JournalMode = 158,
426
427    // === Vacuum ===
428    /// Vacuum the database.
429    Vacuum = 159,
430    /// Incremental vacuum step; jump to P2 if done.
431    IncrVacuum = 160,
432
433    // === Expiry & Locking ===
434    /// Mark prepared statement as expired.
435    Expire = 161,
436    /// Lock cursor P1.
437    CursorLock = 162,
438    /// Unlock cursor P1.
439    CursorUnlock = 163,
440    /// Lock table P2 in database P1.
441    TableLock = 164,
442
443    // === Virtual Table ===
444    /// Begin a virtual table transaction.
445    VBegin = 165,
446    /// Create a virtual table.
447    VCreate = 166,
448    /// Destroy a virtual table.
449    VDestroy = 167,
450    /// Open a virtual table cursor.
451    VOpen = 168,
452    /// Check virtual table integrity.
453    VCheck = 169,
454    /// Initialize IN constraint for virtual table.
455    VInitIn = 170,
456    /// Apply filter to virtual table cursor.
457    VFilter = 171,
458    /// Read column from virtual table cursor.
459    VColumn = 172,
460    /// Advance virtual table cursor.
461    VNext = 173,
462    /// Rename a virtual table.
463    VRename = 174,
464    /// Update/insert/delete on virtual table.
465    VUpdate = 175,
466
467    // === Page Count ===
468    /// Store database page count in register P2.
469    Pagecount = 176,
470    /// Set or read max page count.
471    MaxPgcnt = 177,
472
473    // === Functions ===
474    /// Call a pure (deterministic) function.
475    PureFunc = 178,
476    /// Call a function (possibly with side effects).
477    Function = 179,
478
479    // === Subtype Operations ===
480    /// Clear the subtype from register P1.
481    ClrSubtype = 180,
482    /// Get subtype of P1 into P2.
483    GetSubtype = 181,
484    /// Set subtype of P2 from P1.
485    SetSubtype = 182,
486
487    // === Bloom Filter ===
488    /// Add entry to Bloom filter.
489    FilterAdd = 183,
490    /// Test Bloom filter; jump to P2 if definitely not present.
491    Filter = 184,
492
493    // === Trace & Init ===
494    /// Trace/profile callback.
495    Trace = 185,
496    /// Initialize VDBE program; jump to P2.
497    Init = 186,
498
499    // === Hints & Debug ===
500    /// Provide cursor hint to storage engine.
501    CursorHint = 187,
502    /// Mark that this program can be aborted.
503    Abortable = 188,
504    /// Release register range.
505    ReleaseReg = 189,
506
507    // === Time-travel (SQL:2011 temporal queries) ===
508    /// Set time-travel snapshot on cursor P1.
509    /// P4 carries `TimeTravelCommitSeq(n)` or `TimeTravelTimestamp(ts)`.
510    /// Must immediately follow the `OpenRead` for the same cursor.
511    /// The cursor becomes read-only; DML/DDL through it returns an error.
512    SetSnapshot = 190,
513
514    // === Noop & FrankenSQLite extensions ===
515    /// No operation.
516    Noop = 191,
517    /// Evaluate a literal-pattern LIKE fast path directly against a register.
518    LikeConstFast = 192,
519    /// Count a run of equal first-column index keys, advancing the cursor.
520    CountIndexEqRun = 193,
521
522    // === Superinstructions (bd-perf V2.1) ===
523    /// Fused NewRowid + MakeRecord + Insert for sequential append.
524    ///
525    /// P1 = cursor number
526    /// P2 = first register of column values (same as MakeRecord P1)
527    /// P3 = number of columns (same as MakeRecord P2)
528    /// P5 = Insert flags (OE_* conflict mode in low nibble)
529    ///
530    /// Combines three opcodes into one dispatch:
531    /// 1. Allocate next sequential rowid (using cached last_alloc_rowid)
532    /// 2. Serialize column registers into record blob
533    /// 3. Append to B-tree via table_insert (prechecked absent, append mode)
534    ///
535    /// Guard conditions (codegen must verify before emitting):
536    /// - No secondary indexes on the table
537    /// - No triggers
538    /// - No foreign keys
539    /// - Default ABORT conflict mode (OE_ABORT = 2 in low nibble)
540    /// - No generated/stored columns
541    FusedAppendInsert = 194,
542
543    /// Fused OpenWrite + Last for cursor setup in INSERT programs.
544    /// P1 = cursor, P2 = root page number, P3 = column count, P5 = flags.
545    /// Opens a write cursor and navigates to the last entry for append.
546    FusedOpenWriteLast = 195,
547
548    /// Fused `Integer(p1=lit, p2=reg) + ResultRow(p1=reg, p2=1)` pair.
549    ///
550    /// Emits a single-column result row whose only value is the literal
551    /// integer `p1`, then clears register `p2` (matching the post-`ResultRow`
552    /// side effect of `take_reg_range`, which drains the source register).
553    ///
554    /// P1 = integer literal value
555    /// P2 = source register (written with the literal, then consumed)
556    /// P3 = unused (reserved; must be 0)
557    /// P4 = `P4::None`
558    /// P5 = 0
559    ///
560    /// Correctness contract: byte-equivalent to the unfused pair. Only the
561    /// peephole codegen pass emits this opcode; it MUST verify that the
562    /// immediately-following `ResultRow` consumes exactly the register
563    /// written by `Integer` and outputs exactly one column.
564    FusedLiteralResultRow = 196,
565
566    /// Compute `SUBSTR(column, 1, P4)` directly from a table cursor column.
567    ///
568    /// P1 = cursor number, P2 = logical column index, P3 = output register,
569    /// P4 = `Int(prefix_len)`, P5 = 0.
570    ///
571    /// The engine may fast-path storage TEXT/BLOB payload prefixes without
572    /// materializing the full column. Unsupported storage classes fall back to
573    /// the equivalent scalar `substr(value, 1, prefix_len)` behavior.
574    ColumnSubstrPrefix = 197,
575
576    /// Compute `octet_length(column)` from a table cursor record header.
577    ///
578    /// P1 = cursor number, P2 = logical column index, P3 = output register,
579    /// P4 = `None`, P5 = 0.
580    ///
581    /// TEXT and BLOB byte lengths are encoded by the record serial type, so a
582    /// storage cursor can answer this without expanding overflow payloads or
583    /// allocating the source value. Unsupported storage classes fall back to
584    /// the equivalent scalar `octet_length(value)` behavior.
585    ColumnOctetLength = 198,
586}
587
588impl Opcode {
589    /// Exclusive upper bound on valid opcode discriminants.
590    ///
591    /// Discriminants run `1..=198` (there is no zero opcode), so valid bytes
592    /// are exactly `1..COUNT` and the number of opcodes defined is `COUNT - 1`.
593    pub const COUNT: usize = 199;
594
595    /// Get the opcode name as a static string slice.
596    #[allow(clippy::too_many_lines)]
597    pub const fn name(self) -> &'static str {
598        match self {
599            Self::Goto => "Goto",
600            Self::Gosub => "Gosub",
601            Self::Return => "Return",
602            Self::InitCoroutine => "InitCoroutine",
603            Self::EndCoroutine => "EndCoroutine",
604            Self::Yield => "Yield",
605            Self::HaltIfNull => "HaltIfNull",
606            Self::Halt => "Halt",
607            Self::Integer => "Integer",
608            Self::Int64 => "Int64",
609            Self::Real => "Real",
610            Self::String8 => "String8",
611            Self::String => "String",
612            Self::BeginSubrtn => "BeginSubrtn",
613            Self::Null => "Null",
614            Self::SoftNull => "SoftNull",
615            Self::Blob => "Blob",
616            Self::Variable => "Variable",
617            Self::Move => "Move",
618            Self::Copy => "Copy",
619            Self::SCopy => "SCopy",
620            Self::IntCopy => "IntCopy",
621            Self::FkCheck => "FkCheck",
622            Self::ResultRow => "ResultRow",
623            Self::Concat => "Concat",
624            Self::Add => "Add",
625            Self::Subtract => "Subtract",
626            Self::Multiply => "Multiply",
627            Self::Divide => "Divide",
628            Self::Remainder => "Remainder",
629            Self::CollSeq => "CollSeq",
630            Self::BitAnd => "BitAnd",
631            Self::BitOr => "BitOr",
632            Self::ShiftLeft => "ShiftLeft",
633            Self::ShiftRight => "ShiftRight",
634            Self::AddImm => "AddImm",
635            Self::MustBeInt => "MustBeInt",
636            Self::RealAffinity => "RealAffinity",
637            Self::Cast => "Cast",
638            Self::Eq => "Eq",
639            Self::Ne => "Ne",
640            Self::Lt => "Lt",
641            Self::Le => "Le",
642            Self::Gt => "Gt",
643            Self::Ge => "Ge",
644            Self::ElseEq => "ElseEq",
645            Self::Permutation => "Permutation",
646            Self::Compare => "Compare",
647            Self::Jump => "Jump",
648            Self::And => "And",
649            Self::Or => "Or",
650            Self::IsTrue => "IsTrue",
651            Self::Not => "Not",
652            Self::BitNot => "BitNot",
653            Self::Once => "Once",
654            Self::If => "If",
655            Self::IfNot => "IfNot",
656            Self::IsNull => "IsNull",
657            Self::IsType => "IsType",
658            Self::ZeroOrNull => "ZeroOrNull",
659            Self::NotNull => "NotNull",
660            Self::IfNullRow => "IfNullRow",
661            Self::Offset => "Offset",
662            Self::Column => "Column",
663            Self::TypeCheck => "TypeCheck",
664            Self::Affinity => "Affinity",
665            Self::MakeRecord => "MakeRecord",
666            Self::Count => "Count",
667            Self::Savepoint => "Savepoint",
668            Self::AutoCommit => "AutoCommit",
669            Self::Transaction => "Transaction",
670            Self::ReadCookie => "ReadCookie",
671            Self::SetCookie => "SetCookie",
672            Self::ReopenIdx => "ReopenIdx",
673            Self::OpenRead => "OpenRead",
674            Self::OpenWrite => "OpenWrite",
675            Self::OpenDup => "OpenDup",
676            Self::OpenEphemeral => "OpenEphemeral",
677            Self::OpenAutoindex => "OpenAutoindex",
678            Self::SorterOpen => "SorterOpen",
679            Self::SequenceTest => "SequenceTest",
680            Self::OpenPseudo => "OpenPseudo",
681            Self::Close => "Close",
682            Self::ColumnsUsed => "ColumnsUsed",
683            Self::SeekLT => "SeekLT",
684            Self::SeekLE => "SeekLE",
685            Self::SeekGE => "SeekGE",
686            Self::SeekGT => "SeekGT",
687            Self::SeekScan => "SeekScan",
688            Self::SeekHit => "SeekHit",
689            Self::IfNotOpen => "IfNotOpen",
690            Self::IfNoHope => "IfNoHope",
691            Self::NoConflict => "NoConflict",
692            Self::NotFound => "NotFound",
693            Self::Found => "Found",
694            Self::SeekRowid => "SeekRowid",
695            Self::NotExists => "NotExists",
696            Self::Sequence => "Sequence",
697            Self::NewRowid => "NewRowid",
698            Self::Insert => "Insert",
699            Self::RowCell => "RowCell",
700            Self::Delete => "Delete",
701            Self::ResetCount => "ResetCount",
702            Self::SorterCompare => "SorterCompare",
703            Self::SorterData => "SorterData",
704            Self::RowData => "RowData",
705            Self::Rowid => "Rowid",
706            Self::NullRow => "NullRow",
707            Self::SeekEnd => "SeekEnd",
708            Self::Last => "Last",
709            Self::IfSizeBetween => "IfSizeBetween",
710            Self::SorterSort => "SorterSort",
711            Self::Sort => "Sort",
712            Self::Rewind => "Rewind",
713            Self::IfEmpty => "IfEmpty",
714            Self::SorterNext => "SorterNext",
715            Self::Prev => "Prev",
716            Self::Next => "Next",
717            Self::IdxInsert => "IdxInsert",
718            Self::SorterInsert => "SorterInsert",
719            Self::IdxDelete => "IdxDelete",
720            Self::DeferredSeek => "DeferredSeek",
721            Self::IdxRowid => "IdxRowid",
722            Self::FinishSeek => "FinishSeek",
723            Self::IdxLE => "IdxLE",
724            Self::IdxGT => "IdxGT",
725            Self::IdxLT => "IdxLT",
726            Self::IdxGE => "IdxGE",
727            Self::Destroy => "Destroy",
728            Self::Clear => "Clear",
729            Self::ResetSorter => "ResetSorter",
730            Self::CreateBtree => "CreateBtree",
731            Self::SqlExec => "SqlExec",
732            Self::ParseSchema => "ParseSchema",
733            Self::LoadAnalysis => "LoadAnalysis",
734            Self::DropTable => "DropTable",
735            Self::DropIndex => "DropIndex",
736            Self::DropTrigger => "DropTrigger",
737            Self::IntegrityCk => "IntegrityCk",
738            Self::RowSetAdd => "RowSetAdd",
739            Self::RowSetRead => "RowSetRead",
740            Self::RowSetTest => "RowSetTest",
741            Self::Program => "Program",
742            Self::Param => "Param",
743            Self::FkCounter => "FkCounter",
744            Self::FkIfZero => "FkIfZero",
745            Self::MemMax => "MemMax",
746            Self::IfPos => "IfPos",
747            Self::OffsetLimit => "OffsetLimit",
748            Self::IfNotZero => "IfNotZero",
749            Self::DecrJumpZero => "DecrJumpZero",
750            Self::AggInverse => "AggInverse",
751            Self::AggStep => "AggStep",
752            Self::AggStep1 => "AggStep1",
753            Self::AggValue => "AggValue",
754            Self::AggFinal => "AggFinal",
755            Self::Checkpoint => "Checkpoint",
756            Self::JournalMode => "JournalMode",
757            Self::Vacuum => "Vacuum",
758            Self::IncrVacuum => "IncrVacuum",
759            Self::Expire => "Expire",
760            Self::CursorLock => "CursorLock",
761            Self::CursorUnlock => "CursorUnlock",
762            Self::TableLock => "TableLock",
763            Self::VBegin => "VBegin",
764            Self::VCreate => "VCreate",
765            Self::VDestroy => "VDestroy",
766            Self::VOpen => "VOpen",
767            Self::VCheck => "VCheck",
768            Self::VInitIn => "VInitIn",
769            Self::VFilter => "VFilter",
770            Self::VColumn => "VColumn",
771            Self::VNext => "VNext",
772            Self::VRename => "VRename",
773            Self::VUpdate => "VUpdate",
774            Self::Pagecount => "Pagecount",
775            Self::MaxPgcnt => "MaxPgcnt",
776            Self::PureFunc => "PureFunc",
777            Self::Function => "Function",
778            Self::ClrSubtype => "ClrSubtype",
779            Self::GetSubtype => "GetSubtype",
780            Self::SetSubtype => "SetSubtype",
781            Self::FilterAdd => "FilterAdd",
782            Self::Filter => "Filter",
783            Self::Trace => "Trace",
784            Self::Init => "Init",
785            Self::CursorHint => "CursorHint",
786            Self::Abortable => "Abortable",
787            Self::ReleaseReg => "ReleaseReg",
788            Self::SetSnapshot => "SetSnapshot",
789            Self::Noop => "Noop",
790            Self::LikeConstFast => "LikeConstFast",
791            Self::CountIndexEqRun => "CountIndexEqRun",
792            Self::FusedAppendInsert => "FusedAppendInsert",
793            Self::FusedOpenWriteLast => "FusedOpenWriteLast",
794            Self::FusedLiteralResultRow => "FusedLiteralResultRow",
795            Self::ColumnSubstrPrefix => "ColumnSubstrPrefix",
796            Self::ColumnOctetLength => "ColumnOctetLength",
797        }
798    }
799
800    /// Try to convert a u8 to an Opcode.
801    #[allow(clippy::too_many_lines)]
802    pub const fn from_byte(byte: u8) -> Option<Self> {
803        if byte == 0 || byte as usize >= Self::COUNT {
804            return None;
805        }
806        // SAFETY: All values 1..Opcode::COUNT are valid discriminants.
807        // We verified byte is in range above.
808        // Since the enum is repr(u8) with consecutive values, this is safe.
809        // However, since unsafe is forbidden, we use a match instead.
810        // For now, we accept the compile-time cost of a big match.
811        match byte {
812            1 => Some(Self::Goto),
813            2 => Some(Self::Gosub),
814            3 => Some(Self::Return),
815            4 => Some(Self::InitCoroutine),
816            5 => Some(Self::EndCoroutine),
817            6 => Some(Self::Yield),
818            7 => Some(Self::HaltIfNull),
819            8 => Some(Self::Halt),
820            9 => Some(Self::Integer),
821            10 => Some(Self::Int64),
822            11 => Some(Self::Real),
823            12 => Some(Self::String8),
824            13 => Some(Self::String),
825            14 => Some(Self::BeginSubrtn),
826            15 => Some(Self::Null),
827            16 => Some(Self::SoftNull),
828            17 => Some(Self::Blob),
829            18 => Some(Self::Variable),
830            19 => Some(Self::Move),
831            20 => Some(Self::Copy),
832            21 => Some(Self::SCopy),
833            22 => Some(Self::IntCopy),
834            23 => Some(Self::FkCheck),
835            24 => Some(Self::ResultRow),
836            25 => Some(Self::Concat),
837            26 => Some(Self::Add),
838            27 => Some(Self::Subtract),
839            28 => Some(Self::Multiply),
840            29 => Some(Self::Divide),
841            30 => Some(Self::Remainder),
842            31 => Some(Self::CollSeq),
843            32 => Some(Self::BitAnd),
844            33 => Some(Self::BitOr),
845            34 => Some(Self::ShiftLeft),
846            35 => Some(Self::ShiftRight),
847            36 => Some(Self::AddImm),
848            37 => Some(Self::MustBeInt),
849            38 => Some(Self::RealAffinity),
850            39 => Some(Self::Cast),
851            40 => Some(Self::Eq),
852            41 => Some(Self::Ne),
853            42 => Some(Self::Lt),
854            43 => Some(Self::Le),
855            44 => Some(Self::Gt),
856            45 => Some(Self::Ge),
857            46 => Some(Self::ElseEq),
858            47 => Some(Self::Permutation),
859            48 => Some(Self::Compare),
860            49 => Some(Self::Jump),
861            50 => Some(Self::And),
862            51 => Some(Self::Or),
863            52 => Some(Self::IsTrue),
864            53 => Some(Self::Not),
865            54 => Some(Self::BitNot),
866            55 => Some(Self::Once),
867            56 => Some(Self::If),
868            57 => Some(Self::IfNot),
869            58 => Some(Self::IsNull),
870            59 => Some(Self::IsType),
871            60 => Some(Self::ZeroOrNull),
872            61 => Some(Self::NotNull),
873            62 => Some(Self::IfNullRow),
874            63 => Some(Self::Offset),
875            64 => Some(Self::Column),
876            65 => Some(Self::TypeCheck),
877            66 => Some(Self::Affinity),
878            67 => Some(Self::MakeRecord),
879            68 => Some(Self::Count),
880            69 => Some(Self::Savepoint),
881            70 => Some(Self::AutoCommit),
882            71 => Some(Self::Transaction),
883            72 => Some(Self::ReadCookie),
884            73 => Some(Self::SetCookie),
885            74 => Some(Self::ReopenIdx),
886            75 => Some(Self::OpenRead),
887            76 => Some(Self::OpenWrite),
888            77 => Some(Self::OpenDup),
889            78 => Some(Self::OpenEphemeral),
890            79 => Some(Self::OpenAutoindex),
891            80 => Some(Self::SorterOpen),
892            81 => Some(Self::SequenceTest),
893            82 => Some(Self::OpenPseudo),
894            83 => Some(Self::Close),
895            84 => Some(Self::ColumnsUsed),
896            85 => Some(Self::SeekLT),
897            86 => Some(Self::SeekLE),
898            87 => Some(Self::SeekGE),
899            88 => Some(Self::SeekGT),
900            89 => Some(Self::SeekScan),
901            90 => Some(Self::SeekHit),
902            91 => Some(Self::IfNotOpen),
903            92 => Some(Self::IfNoHope),
904            93 => Some(Self::NoConflict),
905            94 => Some(Self::NotFound),
906            95 => Some(Self::Found),
907            96 => Some(Self::SeekRowid),
908            97 => Some(Self::NotExists),
909            98 => Some(Self::Sequence),
910            99 => Some(Self::NewRowid),
911            100 => Some(Self::Insert),
912            101 => Some(Self::RowCell),
913            102 => Some(Self::Delete),
914            103 => Some(Self::ResetCount),
915            104 => Some(Self::SorterCompare),
916            105 => Some(Self::SorterData),
917            106 => Some(Self::RowData),
918            107 => Some(Self::Rowid),
919            108 => Some(Self::NullRow),
920            109 => Some(Self::SeekEnd),
921            110 => Some(Self::Last),
922            111 => Some(Self::IfSizeBetween),
923            112 => Some(Self::SorterSort),
924            113 => Some(Self::Sort),
925            114 => Some(Self::Rewind),
926            115 => Some(Self::IfEmpty),
927            116 => Some(Self::SorterNext),
928            117 => Some(Self::Prev),
929            118 => Some(Self::Next),
930            119 => Some(Self::IdxInsert),
931            120 => Some(Self::SorterInsert),
932            121 => Some(Self::IdxDelete),
933            122 => Some(Self::DeferredSeek),
934            123 => Some(Self::IdxRowid),
935            124 => Some(Self::FinishSeek),
936            125 => Some(Self::IdxLE),
937            126 => Some(Self::IdxGT),
938            127 => Some(Self::IdxLT),
939            128 => Some(Self::IdxGE),
940            129 => Some(Self::Destroy),
941            130 => Some(Self::Clear),
942            131 => Some(Self::ResetSorter),
943            132 => Some(Self::CreateBtree),
944            133 => Some(Self::SqlExec),
945            134 => Some(Self::ParseSchema),
946            135 => Some(Self::LoadAnalysis),
947            136 => Some(Self::DropTable),
948            137 => Some(Self::DropIndex),
949            138 => Some(Self::DropTrigger),
950            139 => Some(Self::IntegrityCk),
951            140 => Some(Self::RowSetAdd),
952            141 => Some(Self::RowSetRead),
953            142 => Some(Self::RowSetTest),
954            143 => Some(Self::Program),
955            144 => Some(Self::Param),
956            145 => Some(Self::FkCounter),
957            146 => Some(Self::FkIfZero),
958            147 => Some(Self::MemMax),
959            148 => Some(Self::IfPos),
960            149 => Some(Self::OffsetLimit),
961            150 => Some(Self::IfNotZero),
962            151 => Some(Self::DecrJumpZero),
963            152 => Some(Self::AggInverse),
964            153 => Some(Self::AggStep),
965            154 => Some(Self::AggStep1),
966            155 => Some(Self::AggValue),
967            156 => Some(Self::AggFinal),
968            157 => Some(Self::Checkpoint),
969            158 => Some(Self::JournalMode),
970            159 => Some(Self::Vacuum),
971            160 => Some(Self::IncrVacuum),
972            161 => Some(Self::Expire),
973            162 => Some(Self::CursorLock),
974            163 => Some(Self::CursorUnlock),
975            164 => Some(Self::TableLock),
976            165 => Some(Self::VBegin),
977            166 => Some(Self::VCreate),
978            167 => Some(Self::VDestroy),
979            168 => Some(Self::VOpen),
980            169 => Some(Self::VCheck),
981            170 => Some(Self::VInitIn),
982            171 => Some(Self::VFilter),
983            172 => Some(Self::VColumn),
984            173 => Some(Self::VNext),
985            174 => Some(Self::VRename),
986            175 => Some(Self::VUpdate),
987            176 => Some(Self::Pagecount),
988            177 => Some(Self::MaxPgcnt),
989            178 => Some(Self::PureFunc),
990            179 => Some(Self::Function),
991            180 => Some(Self::ClrSubtype),
992            181 => Some(Self::GetSubtype),
993            182 => Some(Self::SetSubtype),
994            183 => Some(Self::FilterAdd),
995            184 => Some(Self::Filter),
996            185 => Some(Self::Trace),
997            186 => Some(Self::Init),
998            187 => Some(Self::CursorHint),
999            188 => Some(Self::Abortable),
1000            189 => Some(Self::ReleaseReg),
1001            190 => Some(Self::SetSnapshot),
1002            191 => Some(Self::Noop),
1003            192 => Some(Self::LikeConstFast),
1004            193 => Some(Self::CountIndexEqRun),
1005            194 => Some(Self::FusedAppendInsert),
1006            195 => Some(Self::FusedOpenWriteLast),
1007            196 => Some(Self::FusedLiteralResultRow),
1008            197 => Some(Self::ColumnSubstrPrefix),
1009            198 => Some(Self::ColumnOctetLength),
1010            _ => None,
1011        }
1012    }
1013
1014    /// Whether this opcode is a jump instruction (has a P2 jump target).
1015    pub const fn is_jump(self) -> bool {
1016        matches!(
1017            self,
1018            Self::Goto
1019                | Self::Gosub
1020                | Self::InitCoroutine
1021                | Self::Yield
1022                | Self::HaltIfNull
1023                | Self::Once
1024                | Self::If
1025                | Self::IfNot
1026                | Self::IsNull
1027                | Self::IsType
1028                | Self::NotNull
1029                | Self::IfNullRow
1030                | Self::Jump
1031                | Self::Eq
1032                | Self::Ne
1033                | Self::Lt
1034                | Self::Le
1035                | Self::Gt
1036                | Self::Ge
1037                | Self::ElseEq
1038                | Self::SeekLT
1039                | Self::SeekLE
1040                | Self::SeekGE
1041                | Self::SeekGT
1042                | Self::SeekRowid
1043                | Self::NotExists
1044                | Self::IfNotOpen
1045                | Self::IfNoHope
1046                | Self::NoConflict
1047                | Self::NotFound
1048                | Self::Found
1049                | Self::Last
1050                | Self::Rewind
1051                | Self::IfEmpty
1052                | Self::IfSizeBetween
1053                | Self::Next
1054                | Self::Prev
1055                | Self::SorterNext
1056                | Self::SorterSort
1057                | Self::Sort
1058                | Self::IdxLE
1059                | Self::IdxGT
1060                | Self::IdxLT
1061                | Self::IdxGE
1062                | Self::RowSetRead
1063                | Self::RowSetTest
1064                | Self::Program
1065                | Self::FkIfZero
1066                | Self::IfPos
1067                | Self::IfNotZero
1068                | Self::DecrJumpZero
1069                | Self::IncrVacuum
1070                | Self::VFilter
1071                | Self::VNext
1072                | Self::Filter
1073                | Self::Init
1074        )
1075    }
1076}
1077
1078impl std::fmt::Display for Opcode {
1079    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1080        f.write_str(self.name())
1081    }
1082}
1083
1084/// A single VDBE instruction.
1085#[derive(Debug, Clone, PartialEq)]
1086pub struct VdbeOp {
1087    /// The opcode.
1088    pub opcode: Opcode,
1089    /// First operand (typically a register number or cursor index).
1090    pub p1: i32,
1091    /// Second operand (often a jump target address).
1092    pub p2: i32,
1093    /// Third operand.
1094    pub p3: i32,
1095    /// Fourth operand (polymorphic: string, function pointer, collation, etc.).
1096    pub p4: P4,
1097    /// Fifth operand (small flags, typically bit flags or type mask).
1098    pub p5: u16,
1099}
1100
1101/// Metadata about an index cursor for REPLACE conflict resolution.
1102///
1103/// Used by `native_replace_row` to clean up secondary index entries when
1104/// a table row is deleted due to REPLACE conflict resolution.
1105#[derive(Debug, Clone, PartialEq, Eq)]
1106pub struct IndexCursorMeta {
1107    /// Cursor ID of the index (typically table_cursor + 1, +2, ...).
1108    pub cursor_id: i32,
1109    /// Column indices (0-based positions in the table schema) that make up
1110    /// the index key. The index key is `(col[0], col[1], ..., rowid)`.
1111    /// Empty denotes a partial or expression index whose persisted entry must
1112    /// be located by its trailing rowid during REPLACE victim cleanup.
1113    pub column_indices: Vec<usize>,
1114}
1115
1116/// The P4 operand of a VDBE instruction.
1117///
1118/// P4 is a polymorphic operand that can hold different types depending on
1119/// the opcode.
1120#[derive(Debug, Clone, PartialEq)]
1121pub enum P4 {
1122    /// No P4 value.
1123    None,
1124    /// A 32-bit integer value.
1125    Int(i32),
1126    /// A 64-bit integer value.
1127    Int64(i64),
1128    /// A 64-bit float value.
1129    Real(f64),
1130    /// A string value.
1131    Str(String),
1132    /// A blob value.
1133    Blob(Vec<u8>),
1134    /// A collation sequence name.
1135    Collation(String),
1136    /// A function name (for Function/PureFunc opcodes).
1137    FuncName(String),
1138    /// A function name with an associated collation sequence for DISTINCT
1139    /// deduplication in aggregate functions (e.g. `COUNT(DISTINCT col)` where
1140    /// `col` has `COLLATE NOCASE`).
1141    FuncNameCollated(String, String),
1142    /// A table name.
1143    Table(String),
1144    /// An index name (for IdxInsert/IdxDelete opcodes).
1145    Index(String),
1146    /// An affinity string (one char per column).
1147    Affinity(String),
1148    /// A precomputed SQLite record header template for `MakeRecord`.
1149    PrecomputedHeader(crate::record::PrecomputedRecordHeader),
1150    /// Time-travel target: commit sequence for `FOR SYSTEM_TIME AS OF COMMITSEQ <n>`.
1151    TimeTravelCommitSeq(u64),
1152    /// Time-travel target: ISO-8601 timestamp for `FOR SYSTEM_TIME AS OF '<ts>'`.
1153    TimeTravelTimestamp(String),
1154}
1155
1156// ── VDBE Program Builder ────────────────────────────────────────────────────
1157//
1158// NOTE: These types intentionally live in `fsqlite-types` so that the planner
1159// (Layer 3) can generate VDBE bytecode without depending on `fsqlite-vdbe`
1160// (Layer 5). This is enforced by the workspace layering tests (bd-1wwc).
1161
1162use fsqlite_error::{FrankenError, Result};
1163use smallvec::SmallVec;
1164
1165/// An opaque handle representing a forward-reference label.
1166///
1167/// Labels allow codegen to emit jump instructions before the target address is
1168/// known. All labels MUST be resolved before execution begins; unresolved
1169/// labels are a codegen bug.
1170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1171pub struct Label(u32);
1172
1173/// Internal tracking for label resolution.
1174#[derive(Debug)]
1175enum LabelState {
1176    /// Not yet resolved. Contains the indices of instructions whose `p2` field
1177    /// should be patched when the label is resolved.
1178    Unresolved(Vec<usize>),
1179    /// Resolved to a concrete instruction address.
1180    Resolved(i32),
1181}
1182
1183/// Sequential register allocator for the VDBE register file.
1184///
1185/// Registers are numbered starting at 1 (register 0 is reserved/unused),
1186/// matching C SQLite convention.
1187#[derive(Debug)]
1188pub struct RegisterAllocator {
1189    /// The next register number to allocate (starts at 1).
1190    next_reg: i32,
1191    /// Pool of returned temporary registers available for reuse.
1192    temp_pool: Vec<i32>,
1193}
1194
1195impl RegisterAllocator {
1196    /// Create a new allocator. First allocation returns register 1.
1197    #[must_use]
1198    pub fn new() -> Self {
1199        Self {
1200            next_reg: 1,
1201            temp_pool: Vec::new(),
1202        }
1203    }
1204
1205    /// Allocate a single persistent register.
1206    pub fn alloc_reg(&mut self) -> i32 {
1207        let reg = self.next_reg;
1208        self.next_reg += 1;
1209        reg
1210    }
1211
1212    /// Allocate a contiguous block of `n` persistent registers.
1213    ///
1214    /// Returns the first register number. The block spans `[result, result+n)`.
1215    pub fn alloc_regs(&mut self, n: i32) -> i32 {
1216        let first = self.next_reg;
1217        self.next_reg += n;
1218        first
1219    }
1220
1221    /// Allocate a temporary register (reuses from pool if available).
1222    pub fn alloc_temp(&mut self) -> i32 {
1223        self.temp_pool.pop().unwrap_or_else(|| {
1224            let reg = self.next_reg;
1225            self.next_reg += 1;
1226            reg
1227        })
1228    }
1229
1230    /// Return a temporary register to the reuse pool.
1231    pub fn free_temp(&mut self, reg: i32) {
1232        self.temp_pool.push(reg);
1233    }
1234
1235    /// The total number of registers allocated (high water mark).
1236    #[must_use]
1237    pub fn count(&self) -> i32 {
1238        self.next_reg - 1
1239    }
1240}
1241
1242impl Default for RegisterAllocator {
1243    fn default() -> Self {
1244        Self::new()
1245    }
1246}
1247
1248/// A VDBE bytecode program under construction.
1249///
1250/// Provides methods to emit instructions, create/resolve labels for forward
1251/// jumps, and allocate registers. Once construction is complete, call
1252/// [`finish`](Self::finish) to validate and extract the final instruction
1253/// sequence.
1254#[derive(Debug)]
1255pub struct ProgramBuilder {
1256    /// The instruction sequence.
1257    ops: SmallVec<[VdbeOp; 64]>,
1258    /// Label states (indexed by `Label.0`).
1259    labels: Vec<LabelState>,
1260    /// Register allocator.
1261    regs: RegisterAllocator,
1262}
1263
1264impl ProgramBuilder {
1265    /// Create a new empty program builder.
1266    #[must_use]
1267    pub fn new() -> Self {
1268        Self {
1269            ops: SmallVec::new(),
1270            labels: Vec::new(),
1271            regs: RegisterAllocator::new(),
1272        }
1273    }
1274
1275    // ── Instruction emission ────────────────────────────────────────────
1276
1277    /// Emit a single instruction and return its address (index in `ops`).
1278    pub fn emit(&mut self, op: VdbeOp) -> usize {
1279        let addr = self.ops.len();
1280        self.ops.push(op);
1281        addr
1282    }
1283
1284    /// Emit a simple instruction from parts.
1285    pub fn emit_op(&mut self, opcode: Opcode, p1: i32, p2: i32, p3: i32, p4: P4, p5: u16) -> usize {
1286        self.emit(VdbeOp {
1287            opcode,
1288            p1,
1289            p2,
1290            p3,
1291            p4,
1292            p5,
1293        })
1294    }
1295
1296    /// The current address (index of the next instruction to be emitted).
1297    #[must_use]
1298    pub fn current_addr(&self) -> usize {
1299        self.ops.len()
1300    }
1301
1302    /// Get a reference to the instruction at `addr`.
1303    #[must_use]
1304    pub fn op_at(&self, addr: usize) -> Option<&VdbeOp> {
1305        self.ops.get(addr)
1306    }
1307
1308    /// Get a mutable reference to the instruction at `addr`.
1309    #[must_use]
1310    pub fn op_at_mut(&mut self, addr: usize) -> Option<&mut VdbeOp> {
1311        self.ops.get_mut(addr)
1312    }
1313
1314    // ── Label system ────────────────────────────────────────────────────
1315
1316    /// Create a new label for forward-reference jumps.
1317    #[must_use]
1318    pub fn emit_label(&mut self) -> Label {
1319        let id = u32::try_from(self.labels.len()).expect("too many labels");
1320        self.labels.push(LabelState::Unresolved(Vec::new()));
1321        Label(id)
1322    }
1323
1324    /// Emit a jump instruction whose p2 target is a label (forward reference).
1325    ///
1326    /// The label's address will be patched into p2 when `resolve_label` is called.
1327    pub fn emit_jump_to_label(
1328        &mut self,
1329        opcode: Opcode,
1330        p1: i32,
1331        p3: i32,
1332        label: Label,
1333        p4: P4,
1334        p5: u16,
1335    ) -> usize {
1336        let addr = self.emit(VdbeOp {
1337            opcode,
1338            p1,
1339            p2: -1, // placeholder; will be patched
1340            p3,
1341            p4,
1342            p5,
1343        });
1344
1345        let state = self
1346            .labels
1347            .get_mut(usize::try_from(label.0).expect("label fits usize"))
1348            .expect("label must exist");
1349
1350        match state {
1351            LabelState::Unresolved(refs) => refs.push(addr),
1352            LabelState::Resolved(target) => {
1353                // Label already resolved; patch immediately.
1354                self.ops[addr].p2 = *target;
1355            }
1356        }
1357
1358        addr
1359    }
1360
1361    /// Resolve a label to the current address and patch all forward refs.
1362    pub fn resolve_label(&mut self, label: Label) {
1363        let addr = i32::try_from(self.current_addr()).expect("program too large");
1364        self.resolve_label_to(label, addr);
1365    }
1366
1367    /// Resolve a label to an explicit address (used for some control patterns).
1368    pub fn resolve_label_to(&mut self, label: Label, address: i32) {
1369        let idx = usize::try_from(label.0).expect("label fits usize");
1370        let state = self.labels.get_mut(idx).expect("label must exist");
1371
1372        match state {
1373            LabelState::Unresolved(refs) => {
1374                // Patch all references.
1375                for &ref_addr in refs.iter() {
1376                    self.ops[ref_addr].p2 = address;
1377                }
1378                *state = LabelState::Resolved(address);
1379            }
1380            LabelState::Resolved(_) => {
1381                // Idempotent: resolving twice is allowed as long as it's consistent.
1382                *state = LabelState::Resolved(address);
1383            }
1384        }
1385    }
1386
1387    // ── Register allocation ─────────────────────────────────────────────
1388
1389    /// Allocate a single persistent register.
1390    pub fn alloc_reg(&mut self) -> i32 {
1391        self.regs.alloc_reg()
1392    }
1393
1394    /// Allocate a contiguous block of persistent registers.
1395    pub fn alloc_regs(&mut self, n: i32) -> i32 {
1396        self.regs.alloc_regs(n)
1397    }
1398
1399    /// Allocate a temporary register (reusable).
1400    pub fn alloc_temp(&mut self) -> i32 {
1401        self.regs.alloc_temp()
1402    }
1403
1404    /// Return a temporary register to the pool.
1405    pub fn free_temp(&mut self, reg: i32) {
1406        self.regs.free_temp(reg);
1407    }
1408
1409    /// Total registers allocated (high water mark).
1410    #[must_use]
1411    pub fn register_count(&self) -> i32 {
1412        self.regs.count()
1413    }
1414
1415    // ── Peephole Passes (IMPL-13) ───────────────────────────────────────
1416
1417    /// Fuse `Integer(lit, reg) + ResultRow(reg, 1)` pairs into
1418    /// `FusedLiteralResultRow(lit, reg)` + `Noop`.
1419    ///
1420    /// Rewrites in-place so program counters, jump targets, and the label
1421    /// tables remain valid without rewiring. The `ResultRow` is replaced by a
1422    /// `Noop` rather than removed so no following instruction shifts.
1423    ///
1424    /// Conservative preconditions per fusion site:
1425    /// - The `Integer`'s target register equals the `ResultRow`'s start
1426    ///   register.
1427    /// - The `ResultRow` emits exactly one column (`p2 == 1`).
1428    /// - The `ResultRow` is NOT a resolved jump target from any prior jump
1429    ///   in this program (a mid-pair jump would otherwise skip the Integer
1430    ///   write and run `ResultRow` against an unrelated register value).
1431    /// - Neither instruction carries a non-`None` P4 payload (Integer/ResultRow
1432    ///   don't use P4 in their canonical form).
1433    /// - Both instructions carry P5 == 0 and P3 == 0.
1434    ///
1435    /// Returns the number of fusions performed.
1436    pub fn apply_fuse_literal_result_row(&mut self) -> usize {
1437        // Collect the set of resolved jump targets. Any address that is the
1438        // target of some jump instruction's `p2` is ineligible to be the
1439        // second half of a fusion pair.
1440        let mut jump_targets: std::collections::HashSet<i32> = std::collections::HashSet::new();
1441        for op in &self.ops {
1442            if op.opcode.is_jump() {
1443                jump_targets.insert(op.p2);
1444            }
1445        }
1446
1447        let mut fused = 0usize;
1448        let len = self.ops.len();
1449        let mut i = 0;
1450        while i + 1 < len {
1451            let is_int = matches!(self.ops[i].opcode, Opcode::Integer)
1452                && self.ops[i].p3 == 0
1453                && self.ops[i].p5 == 0
1454                && matches!(self.ops[i].p4, P4::None);
1455            let is_row = matches!(self.ops[i + 1].opcode, Opcode::ResultRow)
1456                && self.ops[i + 1].p2 == 1
1457                && self.ops[i + 1].p3 == 0
1458                && self.ops[i + 1].p5 == 0
1459                && matches!(self.ops[i + 1].p4, P4::None);
1460            let same_reg = is_int && is_row && self.ops[i].p2 == self.ops[i + 1].p1;
1461            let row_addr = i32::try_from(i + 1).ok();
1462            let row_is_target = row_addr.is_some_and(|a| jump_targets.contains(&a));
1463
1464            if same_reg && !row_is_target {
1465                let lit = self.ops[i].p1;
1466                let reg = self.ops[i].p2;
1467                self.ops[i] = VdbeOp {
1468                    opcode: Opcode::FusedLiteralResultRow,
1469                    p1: lit,
1470                    p2: reg,
1471                    p3: 0,
1472                    p4: P4::None,
1473                    p5: 0,
1474                };
1475                self.ops[i + 1] = VdbeOp {
1476                    opcode: Opcode::Noop,
1477                    p1: 0,
1478                    p2: 0,
1479                    p3: 0,
1480                    p4: P4::None,
1481                    p5: 0,
1482                };
1483                fused += 1;
1484                i += 2;
1485            } else {
1486                i += 1;
1487            }
1488        }
1489        fused
1490    }
1491
1492    // ── Finalization ────────────────────────────────────────────────────
1493
1494    /// Validate all labels are resolved and return the finished program.
1495    pub fn finish(self) -> Result<VdbeProgram> {
1496        // Check for unresolved labels.
1497        for (i, state) in self.labels.iter().enumerate() {
1498            if let LabelState::Unresolved(refs) = state
1499                && !refs.is_empty()
1500            {
1501                return Err(FrankenError::Internal(format!(
1502                    "unresolved label {i} referenced by {} instruction(s)",
1503                    refs.len()
1504                )));
1505            }
1506        }
1507
1508        Ok(VdbeProgram {
1509            ops: self.ops,
1510            register_count: self.regs.count(),
1511        })
1512    }
1513}
1514
1515impl Default for ProgramBuilder {
1516    fn default() -> Self {
1517        Self::new()
1518    }
1519}
1520
1521/// A finalized VDBE bytecode program ready for execution.
1522#[derive(Debug, Clone, PartialEq)]
1523pub struct VdbeProgram {
1524    /// The instruction sequence.
1525    ops: SmallVec<[VdbeOp; 64]>,
1526    /// Number of registers needed (high water mark from allocation).
1527    register_count: i32,
1528}
1529
1530impl VdbeProgram {
1531    /// The instruction sequence.
1532    #[must_use]
1533    pub fn ops(&self) -> &[VdbeOp] {
1534        &self.ops
1535    }
1536
1537    /// Number of instructions.
1538    #[must_use]
1539    pub fn len(&self) -> usize {
1540        self.ops.len()
1541    }
1542
1543    /// Whether the program is empty.
1544    #[must_use]
1545    pub fn is_empty(&self) -> bool {
1546        self.ops.is_empty()
1547    }
1548
1549    /// Number of registers required.
1550    #[must_use]
1551    pub fn register_count(&self) -> i32 {
1552        self.register_count
1553    }
1554
1555    /// Get the instruction at the given program counter.
1556    #[must_use]
1557    pub fn get(&self, pc: usize) -> Option<&VdbeOp> {
1558        self.ops.get(pc)
1559    }
1560
1561    /// Disassemble the program to a human-readable string.
1562    ///
1563    /// Output format matches SQLite's `EXPLAIN` output.
1564    #[must_use]
1565    pub fn disassemble(&self) -> String {
1566        use std::fmt::Write;
1567
1568        let mut out = std::string::String::with_capacity(self.ops.len() * 60);
1569        out.push_str("addr  opcode           p1    p2    p3    p4                 p5\n");
1570        out.push_str("----  ---------------  ----  ----  ----  -----------------  --\n");
1571
1572        for (addr, op) in self.ops.iter().enumerate() {
1573            let p4_str = match &op.p4 {
1574                P4::None => String::new(),
1575                P4::Int(v) => format!("(int){v}"),
1576                P4::Int64(v) => format!("(i64){v}"),
1577                P4::Real(v) => format!("(real){v}"),
1578                P4::Str(s) => format!("(str){s}"),
1579                P4::Blob(b) => format!("(blob)[{}B]", b.len()),
1580                P4::Collation(c) => format!("(coll){c}"),
1581                P4::FuncName(f) => format!("(func){f}"),
1582                P4::FuncNameCollated(f, c) => format!("(func){f} coll={c}"),
1583                P4::Table(t) => format!("(tbl){t}"),
1584                P4::Index(i) => format!("(idx){i}"),
1585                P4::Affinity(a) => format!("(aff){a}"),
1586                P4::PrecomputedHeader(header) => format!("(hdr)[{}B]", header.template.len()),
1587                P4::TimeTravelCommitSeq(seq) => format!("(tt-seq){seq}"),
1588                P4::TimeTravelTimestamp(ts) => format!("(tt-ts){ts}"),
1589            };
1590
1591            writeln!(
1592                &mut out,
1593                "{addr:<4}  {:<15}  {:<4}  {:<4}  {:<4}  {:<17}  {:<2}",
1594                op.opcode.name(),
1595                op.p1,
1596                op.p2,
1597                op.p3,
1598                p4_str,
1599                op.p5,
1600            )
1601            .expect("write to string");
1602        }
1603
1604        out
1605    }
1606}
1607
1608#[cfg(test)]
1609#[allow(clippy::approx_constant)]
1610mod tests {
1611    use super::*;
1612    use std::collections::HashSet;
1613
1614    #[test]
1615    fn opcode_count() {
1616        // COUNT is the exclusive upper bound on discriminants (1..COUNT), so the
1617        // number of opcodes actually defined is COUNT - 1.
1618        assert_eq!(Opcode::COUNT, 199);
1619        assert_eq!(Opcode::COUNT - 1, 198);
1620    }
1621
1622    #[test]
1623    fn opcode_name_roundtrip() {
1624        // Spot check a few opcodes
1625        assert_eq!(Opcode::Goto.name(), "Goto");
1626        assert_eq!(Opcode::Halt.name(), "Halt");
1627        assert_eq!(Opcode::Insert.name(), "Insert");
1628        assert_eq!(Opcode::Delete.name(), "Delete");
1629        assert_eq!(Opcode::ResultRow.name(), "ResultRow");
1630        assert_eq!(Opcode::Noop.name(), "Noop");
1631    }
1632
1633    #[test]
1634    fn opcode_from_byte() {
1635        assert_eq!(Opcode::from_byte(0), None);
1636        assert_eq!(Opcode::from_byte(1), Some(Opcode::Goto));
1637        assert_eq!(Opcode::from_byte(8), Some(Opcode::Halt));
1638        assert_eq!(Opcode::from_byte(190), Some(Opcode::SetSnapshot));
1639        assert_eq!(Opcode::from_byte(191), Some(Opcode::Noop));
1640        assert_eq!(Opcode::from_byte(192), Some(Opcode::LikeConstFast));
1641        assert_eq!(Opcode::from_byte(196), Some(Opcode::FusedLiteralResultRow));
1642        assert_eq!(Opcode::from_byte(197), Some(Opcode::ColumnSubstrPrefix));
1643        assert_eq!(Opcode::from_byte(198), Some(Opcode::ColumnOctetLength));
1644        assert_eq!(Opcode::from_byte(199), None);
1645        assert_eq!(Opcode::from_byte(255), None);
1646    }
1647
1648    #[test]
1649    fn opcode_from_byte_exhaustive() {
1650        // Every assigned opcode byte should produce Some.
1651        for i in 1..Opcode::COUNT as u8 {
1652            assert!(
1653                Opcode::from_byte(i).is_some(),
1654                "from_byte({i}) returned None"
1655            );
1656        }
1657    }
1658
1659    #[test]
1660    fn test_opcode_distinct_u8_values() {
1661        let mut encoded = HashSet::new();
1662        for byte in 1..Opcode::COUNT as u8 {
1663            let opcode = Opcode::from_byte(byte).expect("opcode byte must decode");
1664            let inserted = encoded.insert(opcode as u8);
1665            assert!(inserted, "duplicate opcode byte value for {:?}", opcode);
1666        }
1667
1668        assert_eq!(
1669            encoded.len(),
1670            Opcode::COUNT - 1,
1671            "every opcode must map to a unique byte"
1672        );
1673    }
1674
1675    #[test]
1676    fn opcode_display() {
1677        assert_eq!(Opcode::Goto.to_string(), "Goto");
1678        assert_eq!(Opcode::Init.to_string(), "Init");
1679    }
1680
1681    #[test]
1682    fn opcode_is_jump() {
1683        assert!(Opcode::Goto.is_jump());
1684        assert!(Opcode::If.is_jump());
1685        assert!(Opcode::IfNot.is_jump());
1686        assert!(Opcode::Eq.is_jump());
1687        assert!(Opcode::Next.is_jump());
1688        assert!(Opcode::Rewind.is_jump());
1689        assert!(Opcode::Init.is_jump());
1690
1691        assert!(!Opcode::Integer.is_jump());
1692        assert!(!Opcode::Add.is_jump());
1693        assert!(!Opcode::Insert.is_jump());
1694        assert!(!Opcode::Noop.is_jump());
1695        assert!(!Opcode::ResultRow.is_jump());
1696    }
1697
1698    #[test]
1699    fn vdbe_op_basic() {
1700        let op = VdbeOp {
1701            opcode: Opcode::Integer,
1702            p1: 42,
1703            p2: 1,
1704            p3: 0,
1705            p4: P4::None,
1706            p5: 0,
1707        };
1708        assert_eq!(op.opcode, Opcode::Integer);
1709        assert_eq!(op.p1, 42);
1710    }
1711
1712    #[test]
1713    fn p4_variants() {
1714        let p4 = P4::Int(42);
1715        assert_eq!(p4, P4::Int(42));
1716
1717        let p4 = P4::Str("hello".to_owned());
1718        assert_eq!(p4, P4::Str("hello".to_owned()));
1719
1720        let p4 = P4::Real(3.14);
1721        assert_eq!(p4, P4::Real(3.14));
1722    }
1723}