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    /// Jump if the most recent row-insert was suppressed by `OE_IGNORE`.
588    ///
589    /// P1 = unused, P2 = jump target. Branches to P2 when the engine's
590    /// `conflict_skip_idx` flag is set — i.e. the preceding `Insert`/`IdxInsert`
591    /// sequence for the current row hit a rowid or UNIQUE conflict under
592    /// `INSERT/UPDATE OR IGNORE` and was rolled back. Used by `UPDATE OR IGNORE
593    /// ... RETURNING` on rowid tables to skip the `RETURNING` emission for a row
594    /// that was ignored (GH #159), reusing the engine's exact conflict decision
595    /// rather than re-deriving uniqueness in codegen.
596    IfConflictSkip = 199,
597}
598
599impl Opcode {
600    /// Exclusive upper bound on valid opcode discriminants.
601    ///
602    /// Discriminants run `1..=199` (there is no zero opcode), so valid bytes
603    /// are exactly `1..COUNT` and the number of opcodes defined is `COUNT - 1`.
604    pub const COUNT: usize = 200;
605
606    /// Get the opcode name as a static string slice.
607    #[allow(clippy::too_many_lines)]
608    pub const fn name(self) -> &'static str {
609        match self {
610            Self::Goto => "Goto",
611            Self::Gosub => "Gosub",
612            Self::Return => "Return",
613            Self::InitCoroutine => "InitCoroutine",
614            Self::EndCoroutine => "EndCoroutine",
615            Self::Yield => "Yield",
616            Self::HaltIfNull => "HaltIfNull",
617            Self::Halt => "Halt",
618            Self::Integer => "Integer",
619            Self::Int64 => "Int64",
620            Self::Real => "Real",
621            Self::String8 => "String8",
622            Self::String => "String",
623            Self::BeginSubrtn => "BeginSubrtn",
624            Self::Null => "Null",
625            Self::SoftNull => "SoftNull",
626            Self::Blob => "Blob",
627            Self::Variable => "Variable",
628            Self::Move => "Move",
629            Self::Copy => "Copy",
630            Self::SCopy => "SCopy",
631            Self::IntCopy => "IntCopy",
632            Self::FkCheck => "FkCheck",
633            Self::ResultRow => "ResultRow",
634            Self::Concat => "Concat",
635            Self::Add => "Add",
636            Self::Subtract => "Subtract",
637            Self::Multiply => "Multiply",
638            Self::Divide => "Divide",
639            Self::Remainder => "Remainder",
640            Self::CollSeq => "CollSeq",
641            Self::BitAnd => "BitAnd",
642            Self::BitOr => "BitOr",
643            Self::ShiftLeft => "ShiftLeft",
644            Self::ShiftRight => "ShiftRight",
645            Self::AddImm => "AddImm",
646            Self::MustBeInt => "MustBeInt",
647            Self::RealAffinity => "RealAffinity",
648            Self::Cast => "Cast",
649            Self::Eq => "Eq",
650            Self::Ne => "Ne",
651            Self::Lt => "Lt",
652            Self::Le => "Le",
653            Self::Gt => "Gt",
654            Self::Ge => "Ge",
655            Self::ElseEq => "ElseEq",
656            Self::Permutation => "Permutation",
657            Self::Compare => "Compare",
658            Self::Jump => "Jump",
659            Self::And => "And",
660            Self::Or => "Or",
661            Self::IsTrue => "IsTrue",
662            Self::Not => "Not",
663            Self::BitNot => "BitNot",
664            Self::Once => "Once",
665            Self::If => "If",
666            Self::IfNot => "IfNot",
667            Self::IsNull => "IsNull",
668            Self::IsType => "IsType",
669            Self::ZeroOrNull => "ZeroOrNull",
670            Self::NotNull => "NotNull",
671            Self::IfNullRow => "IfNullRow",
672            Self::Offset => "Offset",
673            Self::Column => "Column",
674            Self::TypeCheck => "TypeCheck",
675            Self::Affinity => "Affinity",
676            Self::MakeRecord => "MakeRecord",
677            Self::Count => "Count",
678            Self::Savepoint => "Savepoint",
679            Self::AutoCommit => "AutoCommit",
680            Self::Transaction => "Transaction",
681            Self::ReadCookie => "ReadCookie",
682            Self::SetCookie => "SetCookie",
683            Self::ReopenIdx => "ReopenIdx",
684            Self::OpenRead => "OpenRead",
685            Self::OpenWrite => "OpenWrite",
686            Self::OpenDup => "OpenDup",
687            Self::OpenEphemeral => "OpenEphemeral",
688            Self::OpenAutoindex => "OpenAutoindex",
689            Self::SorterOpen => "SorterOpen",
690            Self::SequenceTest => "SequenceTest",
691            Self::OpenPseudo => "OpenPseudo",
692            Self::Close => "Close",
693            Self::ColumnsUsed => "ColumnsUsed",
694            Self::SeekLT => "SeekLT",
695            Self::SeekLE => "SeekLE",
696            Self::SeekGE => "SeekGE",
697            Self::SeekGT => "SeekGT",
698            Self::SeekScan => "SeekScan",
699            Self::SeekHit => "SeekHit",
700            Self::IfNotOpen => "IfNotOpen",
701            Self::IfNoHope => "IfNoHope",
702            Self::NoConflict => "NoConflict",
703            Self::NotFound => "NotFound",
704            Self::Found => "Found",
705            Self::SeekRowid => "SeekRowid",
706            Self::NotExists => "NotExists",
707            Self::Sequence => "Sequence",
708            Self::NewRowid => "NewRowid",
709            Self::Insert => "Insert",
710            Self::RowCell => "RowCell",
711            Self::Delete => "Delete",
712            Self::ResetCount => "ResetCount",
713            Self::SorterCompare => "SorterCompare",
714            Self::SorterData => "SorterData",
715            Self::RowData => "RowData",
716            Self::Rowid => "Rowid",
717            Self::NullRow => "NullRow",
718            Self::SeekEnd => "SeekEnd",
719            Self::Last => "Last",
720            Self::IfSizeBetween => "IfSizeBetween",
721            Self::SorterSort => "SorterSort",
722            Self::Sort => "Sort",
723            Self::Rewind => "Rewind",
724            Self::IfEmpty => "IfEmpty",
725            Self::SorterNext => "SorterNext",
726            Self::Prev => "Prev",
727            Self::Next => "Next",
728            Self::IdxInsert => "IdxInsert",
729            Self::SorterInsert => "SorterInsert",
730            Self::IdxDelete => "IdxDelete",
731            Self::DeferredSeek => "DeferredSeek",
732            Self::IdxRowid => "IdxRowid",
733            Self::FinishSeek => "FinishSeek",
734            Self::IdxLE => "IdxLE",
735            Self::IdxGT => "IdxGT",
736            Self::IdxLT => "IdxLT",
737            Self::IdxGE => "IdxGE",
738            Self::Destroy => "Destroy",
739            Self::Clear => "Clear",
740            Self::ResetSorter => "ResetSorter",
741            Self::CreateBtree => "CreateBtree",
742            Self::SqlExec => "SqlExec",
743            Self::ParseSchema => "ParseSchema",
744            Self::LoadAnalysis => "LoadAnalysis",
745            Self::DropTable => "DropTable",
746            Self::DropIndex => "DropIndex",
747            Self::DropTrigger => "DropTrigger",
748            Self::IntegrityCk => "IntegrityCk",
749            Self::RowSetAdd => "RowSetAdd",
750            Self::RowSetRead => "RowSetRead",
751            Self::RowSetTest => "RowSetTest",
752            Self::Program => "Program",
753            Self::Param => "Param",
754            Self::FkCounter => "FkCounter",
755            Self::FkIfZero => "FkIfZero",
756            Self::MemMax => "MemMax",
757            Self::IfPos => "IfPos",
758            Self::OffsetLimit => "OffsetLimit",
759            Self::IfNotZero => "IfNotZero",
760            Self::DecrJumpZero => "DecrJumpZero",
761            Self::AggInverse => "AggInverse",
762            Self::AggStep => "AggStep",
763            Self::AggStep1 => "AggStep1",
764            Self::AggValue => "AggValue",
765            Self::AggFinal => "AggFinal",
766            Self::Checkpoint => "Checkpoint",
767            Self::JournalMode => "JournalMode",
768            Self::Vacuum => "Vacuum",
769            Self::IncrVacuum => "IncrVacuum",
770            Self::Expire => "Expire",
771            Self::CursorLock => "CursorLock",
772            Self::CursorUnlock => "CursorUnlock",
773            Self::TableLock => "TableLock",
774            Self::VBegin => "VBegin",
775            Self::VCreate => "VCreate",
776            Self::VDestroy => "VDestroy",
777            Self::VOpen => "VOpen",
778            Self::VCheck => "VCheck",
779            Self::VInitIn => "VInitIn",
780            Self::VFilter => "VFilter",
781            Self::VColumn => "VColumn",
782            Self::VNext => "VNext",
783            Self::VRename => "VRename",
784            Self::VUpdate => "VUpdate",
785            Self::Pagecount => "Pagecount",
786            Self::MaxPgcnt => "MaxPgcnt",
787            Self::PureFunc => "PureFunc",
788            Self::Function => "Function",
789            Self::ClrSubtype => "ClrSubtype",
790            Self::GetSubtype => "GetSubtype",
791            Self::SetSubtype => "SetSubtype",
792            Self::FilterAdd => "FilterAdd",
793            Self::Filter => "Filter",
794            Self::Trace => "Trace",
795            Self::Init => "Init",
796            Self::CursorHint => "CursorHint",
797            Self::Abortable => "Abortable",
798            Self::ReleaseReg => "ReleaseReg",
799            Self::SetSnapshot => "SetSnapshot",
800            Self::Noop => "Noop",
801            Self::LikeConstFast => "LikeConstFast",
802            Self::CountIndexEqRun => "CountIndexEqRun",
803            Self::FusedAppendInsert => "FusedAppendInsert",
804            Self::FusedOpenWriteLast => "FusedOpenWriteLast",
805            Self::FusedLiteralResultRow => "FusedLiteralResultRow",
806            Self::ColumnSubstrPrefix => "ColumnSubstrPrefix",
807            Self::ColumnOctetLength => "ColumnOctetLength",
808            Self::IfConflictSkip => "IfConflictSkip",
809        }
810    }
811
812    /// Try to convert a u8 to an Opcode.
813    #[allow(clippy::too_many_lines)]
814    pub const fn from_byte(byte: u8) -> Option<Self> {
815        if byte == 0 || byte as usize >= Self::COUNT {
816            return None;
817        }
818        // SAFETY: All values 1..Opcode::COUNT are valid discriminants.
819        // We verified byte is in range above.
820        // Since the enum is repr(u8) with consecutive values, this is safe.
821        // However, since unsafe is forbidden, we use a match instead.
822        // For now, we accept the compile-time cost of a big match.
823        match byte {
824            1 => Some(Self::Goto),
825            2 => Some(Self::Gosub),
826            3 => Some(Self::Return),
827            4 => Some(Self::InitCoroutine),
828            5 => Some(Self::EndCoroutine),
829            6 => Some(Self::Yield),
830            7 => Some(Self::HaltIfNull),
831            8 => Some(Self::Halt),
832            9 => Some(Self::Integer),
833            10 => Some(Self::Int64),
834            11 => Some(Self::Real),
835            12 => Some(Self::String8),
836            13 => Some(Self::String),
837            14 => Some(Self::BeginSubrtn),
838            15 => Some(Self::Null),
839            16 => Some(Self::SoftNull),
840            17 => Some(Self::Blob),
841            18 => Some(Self::Variable),
842            19 => Some(Self::Move),
843            20 => Some(Self::Copy),
844            21 => Some(Self::SCopy),
845            22 => Some(Self::IntCopy),
846            23 => Some(Self::FkCheck),
847            24 => Some(Self::ResultRow),
848            25 => Some(Self::Concat),
849            26 => Some(Self::Add),
850            27 => Some(Self::Subtract),
851            28 => Some(Self::Multiply),
852            29 => Some(Self::Divide),
853            30 => Some(Self::Remainder),
854            31 => Some(Self::CollSeq),
855            32 => Some(Self::BitAnd),
856            33 => Some(Self::BitOr),
857            34 => Some(Self::ShiftLeft),
858            35 => Some(Self::ShiftRight),
859            36 => Some(Self::AddImm),
860            37 => Some(Self::MustBeInt),
861            38 => Some(Self::RealAffinity),
862            39 => Some(Self::Cast),
863            40 => Some(Self::Eq),
864            41 => Some(Self::Ne),
865            42 => Some(Self::Lt),
866            43 => Some(Self::Le),
867            44 => Some(Self::Gt),
868            45 => Some(Self::Ge),
869            46 => Some(Self::ElseEq),
870            47 => Some(Self::Permutation),
871            48 => Some(Self::Compare),
872            49 => Some(Self::Jump),
873            50 => Some(Self::And),
874            51 => Some(Self::Or),
875            52 => Some(Self::IsTrue),
876            53 => Some(Self::Not),
877            54 => Some(Self::BitNot),
878            55 => Some(Self::Once),
879            56 => Some(Self::If),
880            57 => Some(Self::IfNot),
881            58 => Some(Self::IsNull),
882            59 => Some(Self::IsType),
883            60 => Some(Self::ZeroOrNull),
884            61 => Some(Self::NotNull),
885            62 => Some(Self::IfNullRow),
886            63 => Some(Self::Offset),
887            64 => Some(Self::Column),
888            65 => Some(Self::TypeCheck),
889            66 => Some(Self::Affinity),
890            67 => Some(Self::MakeRecord),
891            68 => Some(Self::Count),
892            69 => Some(Self::Savepoint),
893            70 => Some(Self::AutoCommit),
894            71 => Some(Self::Transaction),
895            72 => Some(Self::ReadCookie),
896            73 => Some(Self::SetCookie),
897            74 => Some(Self::ReopenIdx),
898            75 => Some(Self::OpenRead),
899            76 => Some(Self::OpenWrite),
900            77 => Some(Self::OpenDup),
901            78 => Some(Self::OpenEphemeral),
902            79 => Some(Self::OpenAutoindex),
903            80 => Some(Self::SorterOpen),
904            81 => Some(Self::SequenceTest),
905            82 => Some(Self::OpenPseudo),
906            83 => Some(Self::Close),
907            84 => Some(Self::ColumnsUsed),
908            85 => Some(Self::SeekLT),
909            86 => Some(Self::SeekLE),
910            87 => Some(Self::SeekGE),
911            88 => Some(Self::SeekGT),
912            89 => Some(Self::SeekScan),
913            90 => Some(Self::SeekHit),
914            91 => Some(Self::IfNotOpen),
915            92 => Some(Self::IfNoHope),
916            93 => Some(Self::NoConflict),
917            94 => Some(Self::NotFound),
918            95 => Some(Self::Found),
919            96 => Some(Self::SeekRowid),
920            97 => Some(Self::NotExists),
921            98 => Some(Self::Sequence),
922            99 => Some(Self::NewRowid),
923            100 => Some(Self::Insert),
924            101 => Some(Self::RowCell),
925            102 => Some(Self::Delete),
926            103 => Some(Self::ResetCount),
927            104 => Some(Self::SorterCompare),
928            105 => Some(Self::SorterData),
929            106 => Some(Self::RowData),
930            107 => Some(Self::Rowid),
931            108 => Some(Self::NullRow),
932            109 => Some(Self::SeekEnd),
933            110 => Some(Self::Last),
934            111 => Some(Self::IfSizeBetween),
935            112 => Some(Self::SorterSort),
936            113 => Some(Self::Sort),
937            114 => Some(Self::Rewind),
938            115 => Some(Self::IfEmpty),
939            116 => Some(Self::SorterNext),
940            117 => Some(Self::Prev),
941            118 => Some(Self::Next),
942            119 => Some(Self::IdxInsert),
943            120 => Some(Self::SorterInsert),
944            121 => Some(Self::IdxDelete),
945            122 => Some(Self::DeferredSeek),
946            123 => Some(Self::IdxRowid),
947            124 => Some(Self::FinishSeek),
948            125 => Some(Self::IdxLE),
949            126 => Some(Self::IdxGT),
950            127 => Some(Self::IdxLT),
951            128 => Some(Self::IdxGE),
952            129 => Some(Self::Destroy),
953            130 => Some(Self::Clear),
954            131 => Some(Self::ResetSorter),
955            132 => Some(Self::CreateBtree),
956            133 => Some(Self::SqlExec),
957            134 => Some(Self::ParseSchema),
958            135 => Some(Self::LoadAnalysis),
959            136 => Some(Self::DropTable),
960            137 => Some(Self::DropIndex),
961            138 => Some(Self::DropTrigger),
962            139 => Some(Self::IntegrityCk),
963            140 => Some(Self::RowSetAdd),
964            141 => Some(Self::RowSetRead),
965            142 => Some(Self::RowSetTest),
966            143 => Some(Self::Program),
967            144 => Some(Self::Param),
968            145 => Some(Self::FkCounter),
969            146 => Some(Self::FkIfZero),
970            147 => Some(Self::MemMax),
971            148 => Some(Self::IfPos),
972            149 => Some(Self::OffsetLimit),
973            150 => Some(Self::IfNotZero),
974            151 => Some(Self::DecrJumpZero),
975            152 => Some(Self::AggInverse),
976            153 => Some(Self::AggStep),
977            154 => Some(Self::AggStep1),
978            155 => Some(Self::AggValue),
979            156 => Some(Self::AggFinal),
980            157 => Some(Self::Checkpoint),
981            158 => Some(Self::JournalMode),
982            159 => Some(Self::Vacuum),
983            160 => Some(Self::IncrVacuum),
984            161 => Some(Self::Expire),
985            162 => Some(Self::CursorLock),
986            163 => Some(Self::CursorUnlock),
987            164 => Some(Self::TableLock),
988            165 => Some(Self::VBegin),
989            166 => Some(Self::VCreate),
990            167 => Some(Self::VDestroy),
991            168 => Some(Self::VOpen),
992            169 => Some(Self::VCheck),
993            170 => Some(Self::VInitIn),
994            171 => Some(Self::VFilter),
995            172 => Some(Self::VColumn),
996            173 => Some(Self::VNext),
997            174 => Some(Self::VRename),
998            175 => Some(Self::VUpdate),
999            176 => Some(Self::Pagecount),
1000            177 => Some(Self::MaxPgcnt),
1001            178 => Some(Self::PureFunc),
1002            179 => Some(Self::Function),
1003            180 => Some(Self::ClrSubtype),
1004            181 => Some(Self::GetSubtype),
1005            182 => Some(Self::SetSubtype),
1006            183 => Some(Self::FilterAdd),
1007            184 => Some(Self::Filter),
1008            185 => Some(Self::Trace),
1009            186 => Some(Self::Init),
1010            187 => Some(Self::CursorHint),
1011            188 => Some(Self::Abortable),
1012            189 => Some(Self::ReleaseReg),
1013            190 => Some(Self::SetSnapshot),
1014            191 => Some(Self::Noop),
1015            192 => Some(Self::LikeConstFast),
1016            193 => Some(Self::CountIndexEqRun),
1017            194 => Some(Self::FusedAppendInsert),
1018            195 => Some(Self::FusedOpenWriteLast),
1019            196 => Some(Self::FusedLiteralResultRow),
1020            197 => Some(Self::ColumnSubstrPrefix),
1021            198 => Some(Self::ColumnOctetLength),
1022            199 => Some(Self::IfConflictSkip),
1023            _ => None,
1024        }
1025    }
1026
1027    /// Whether this opcode is a jump instruction (has a P2 jump target).
1028    pub const fn is_jump(self) -> bool {
1029        matches!(
1030            self,
1031            Self::Goto
1032                | Self::Gosub
1033                | Self::InitCoroutine
1034                | Self::Yield
1035                | Self::HaltIfNull
1036                | Self::Once
1037                | Self::If
1038                | Self::IfNot
1039                | Self::IsNull
1040                | Self::IsType
1041                | Self::NotNull
1042                | Self::IfNullRow
1043                | Self::Jump
1044                | Self::Eq
1045                | Self::Ne
1046                | Self::Lt
1047                | Self::Le
1048                | Self::Gt
1049                | Self::Ge
1050                | Self::ElseEq
1051                | Self::SeekLT
1052                | Self::SeekLE
1053                | Self::SeekGE
1054                | Self::SeekGT
1055                | Self::SeekRowid
1056                | Self::NotExists
1057                | Self::IfNotOpen
1058                | Self::IfNoHope
1059                | Self::NoConflict
1060                | Self::NotFound
1061                | Self::Found
1062                | Self::Last
1063                | Self::Rewind
1064                | Self::IfEmpty
1065                | Self::IfSizeBetween
1066                | Self::Next
1067                | Self::Prev
1068                | Self::SorterNext
1069                | Self::SorterSort
1070                | Self::Sort
1071                | Self::IdxLE
1072                | Self::IdxGT
1073                | Self::IdxLT
1074                | Self::IdxGE
1075                | Self::RowSetRead
1076                | Self::RowSetTest
1077                | Self::Program
1078                | Self::FkIfZero
1079                | Self::IfPos
1080                | Self::IfNotZero
1081                | Self::DecrJumpZero
1082                | Self::IncrVacuum
1083                | Self::VFilter
1084                | Self::VNext
1085                | Self::Filter
1086                | Self::Init
1087                | Self::IfConflictSkip
1088        )
1089    }
1090}
1091
1092impl std::fmt::Display for Opcode {
1093    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1094        f.write_str(self.name())
1095    }
1096}
1097
1098/// A single VDBE instruction.
1099#[derive(Debug, Clone, PartialEq)]
1100pub struct VdbeOp {
1101    /// The opcode.
1102    pub opcode: Opcode,
1103    /// First operand (typically a register number or cursor index).
1104    pub p1: i32,
1105    /// Second operand (often a jump target address).
1106    pub p2: i32,
1107    /// Third operand.
1108    pub p3: i32,
1109    /// Fourth operand (polymorphic: string, function pointer, collation, etc.).
1110    pub p4: P4,
1111    /// Fifth operand (small flags, typically bit flags or type mask).
1112    pub p5: u16,
1113}
1114
1115/// Metadata about an index cursor for REPLACE conflict resolution.
1116///
1117/// Used by `native_replace_row` to clean up secondary index entries when
1118/// a table row is deleted due to REPLACE conflict resolution.
1119#[derive(Debug, Clone, PartialEq, Eq)]
1120pub struct IndexCursorMeta {
1121    /// Cursor ID of the index (typically table_cursor + 1, +2, ...).
1122    pub cursor_id: i32,
1123    /// Column indices (0-based positions in the table schema) that make up
1124    /// the index key. The index key is `(col[0], col[1], ..., rowid)`.
1125    /// Empty denotes a partial or expression index whose persisted entry must
1126    /// be located by its trailing rowid during REPLACE victim cleanup.
1127    pub column_indices: Vec<usize>,
1128}
1129
1130/// The P4 operand of a VDBE instruction.
1131///
1132/// P4 is a polymorphic operand that can hold different types depending on
1133/// the opcode.
1134#[derive(Debug, Clone, PartialEq)]
1135pub enum P4 {
1136    /// No P4 value.
1137    None,
1138    /// A 32-bit integer value.
1139    Int(i32),
1140    /// A 64-bit integer value.
1141    Int64(i64),
1142    /// A 64-bit float value.
1143    Real(f64),
1144    /// A string value.
1145    Str(String),
1146    /// A blob value.
1147    Blob(Vec<u8>),
1148    /// A collation sequence name.
1149    Collation(String),
1150    /// A function name (for Function/PureFunc opcodes).
1151    FuncName(String),
1152    /// A function name with an associated collation sequence for DISTINCT
1153    /// deduplication in aggregate functions (e.g. `COUNT(DISTINCT col)` where
1154    /// `col` has `COLLATE NOCASE`).
1155    FuncNameCollated(String, String),
1156    /// A table name.
1157    Table(String),
1158    /// An index name (for IdxInsert/IdxDelete opcodes).
1159    Index(String),
1160    /// An affinity string (one char per column).
1161    Affinity(String),
1162    /// A precomputed SQLite record header template for `MakeRecord`.
1163    PrecomputedHeader(crate::record::PrecomputedRecordHeader),
1164    /// Time-travel target: commit sequence for `FOR SYSTEM_TIME AS OF COMMITSEQ <n>`.
1165    TimeTravelCommitSeq(u64),
1166    /// Time-travel target: ISO-8601 timestamp for `FOR SYSTEM_TIME AS OF '<ts>'`.
1167    TimeTravelTimestamp(String),
1168}
1169
1170// ── VDBE Program Builder ────────────────────────────────────────────────────
1171//
1172// NOTE: These types intentionally live in `fsqlite-types` so that the planner
1173// (Layer 3) can generate VDBE bytecode without depending on `fsqlite-vdbe`
1174// (Layer 5). This is enforced by the workspace layering tests (bd-1wwc).
1175
1176use fsqlite_error::{FrankenError, Result};
1177use smallvec::SmallVec;
1178
1179/// An opaque handle representing a forward-reference label.
1180///
1181/// Labels allow codegen to emit jump instructions before the target address is
1182/// known. All labels MUST be resolved before execution begins; unresolved
1183/// labels are a codegen bug.
1184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1185pub struct Label(u32);
1186
1187/// Internal tracking for label resolution.
1188#[derive(Debug)]
1189enum LabelState {
1190    /// Not yet resolved. Contains the indices of instructions whose `p2` field
1191    /// should be patched when the label is resolved.
1192    Unresolved(Vec<usize>),
1193    /// Resolved to a concrete instruction address.
1194    Resolved(i32),
1195}
1196
1197/// Sequential register allocator for the VDBE register file.
1198///
1199/// Registers are numbered starting at 1 (register 0 is reserved/unused),
1200/// matching C SQLite convention.
1201#[derive(Debug)]
1202pub struct RegisterAllocator {
1203    /// The next register number to allocate (starts at 1).
1204    next_reg: i32,
1205    /// Pool of returned temporary registers available for reuse.
1206    temp_pool: Vec<i32>,
1207}
1208
1209impl RegisterAllocator {
1210    /// Create a new allocator. First allocation returns register 1.
1211    #[must_use]
1212    pub fn new() -> Self {
1213        Self {
1214            next_reg: 1,
1215            temp_pool: Vec::new(),
1216        }
1217    }
1218
1219    /// Allocate a single persistent register.
1220    pub fn alloc_reg(&mut self) -> i32 {
1221        let reg = self.next_reg;
1222        self.next_reg += 1;
1223        reg
1224    }
1225
1226    /// Allocate a contiguous block of `n` persistent registers.
1227    ///
1228    /// Returns the first register number. The block spans `[result, result+n)`.
1229    pub fn alloc_regs(&mut self, n: i32) -> i32 {
1230        let first = self.next_reg;
1231        self.next_reg += n;
1232        first
1233    }
1234
1235    /// Allocate a temporary register (reuses from pool if available).
1236    pub fn alloc_temp(&mut self) -> i32 {
1237        self.temp_pool.pop().unwrap_or_else(|| {
1238            let reg = self.next_reg;
1239            self.next_reg += 1;
1240            reg
1241        })
1242    }
1243
1244    /// Return a temporary register to the reuse pool.
1245    pub fn free_temp(&mut self, reg: i32) {
1246        self.temp_pool.push(reg);
1247    }
1248
1249    /// The total number of registers allocated (high water mark).
1250    #[must_use]
1251    pub fn count(&self) -> i32 {
1252        self.next_reg - 1
1253    }
1254}
1255
1256impl Default for RegisterAllocator {
1257    fn default() -> Self {
1258        Self::new()
1259    }
1260}
1261
1262/// A VDBE bytecode program under construction.
1263///
1264/// Provides methods to emit instructions, create/resolve labels for forward
1265/// jumps, and allocate registers. Once construction is complete, call
1266/// [`finish`](Self::finish) to validate and extract the final instruction
1267/// sequence.
1268#[derive(Debug)]
1269pub struct ProgramBuilder {
1270    /// The instruction sequence.
1271    ops: SmallVec<[VdbeOp; 64]>,
1272    /// Label states (indexed by `Label.0`).
1273    labels: Vec<LabelState>,
1274    /// Register allocator.
1275    regs: RegisterAllocator,
1276}
1277
1278impl ProgramBuilder {
1279    /// Create a new empty program builder.
1280    #[must_use]
1281    pub fn new() -> Self {
1282        Self {
1283            ops: SmallVec::new(),
1284            labels: Vec::new(),
1285            regs: RegisterAllocator::new(),
1286        }
1287    }
1288
1289    // ── Instruction emission ────────────────────────────────────────────
1290
1291    /// Emit a single instruction and return its address (index in `ops`).
1292    pub fn emit(&mut self, op: VdbeOp) -> usize {
1293        let addr = self.ops.len();
1294        self.ops.push(op);
1295        addr
1296    }
1297
1298    /// Emit a simple instruction from parts.
1299    pub fn emit_op(&mut self, opcode: Opcode, p1: i32, p2: i32, p3: i32, p4: P4, p5: u16) -> usize {
1300        self.emit(VdbeOp {
1301            opcode,
1302            p1,
1303            p2,
1304            p3,
1305            p4,
1306            p5,
1307        })
1308    }
1309
1310    /// The current address (index of the next instruction to be emitted).
1311    #[must_use]
1312    pub fn current_addr(&self) -> usize {
1313        self.ops.len()
1314    }
1315
1316    /// Get a reference to the instruction at `addr`.
1317    #[must_use]
1318    pub fn op_at(&self, addr: usize) -> Option<&VdbeOp> {
1319        self.ops.get(addr)
1320    }
1321
1322    /// Get a mutable reference to the instruction at `addr`.
1323    #[must_use]
1324    pub fn op_at_mut(&mut self, addr: usize) -> Option<&mut VdbeOp> {
1325        self.ops.get_mut(addr)
1326    }
1327
1328    // ── Label system ────────────────────────────────────────────────────
1329
1330    /// Create a new label for forward-reference jumps.
1331    #[must_use]
1332    pub fn emit_label(&mut self) -> Label {
1333        let id = u32::try_from(self.labels.len()).expect("too many labels");
1334        self.labels.push(LabelState::Unresolved(Vec::new()));
1335        Label(id)
1336    }
1337
1338    /// Emit a jump instruction whose p2 target is a label (forward reference).
1339    ///
1340    /// The label's address will be patched into p2 when `resolve_label` is called.
1341    pub fn emit_jump_to_label(
1342        &mut self,
1343        opcode: Opcode,
1344        p1: i32,
1345        p3: i32,
1346        label: Label,
1347        p4: P4,
1348        p5: u16,
1349    ) -> usize {
1350        let addr = self.emit(VdbeOp {
1351            opcode,
1352            p1,
1353            p2: -1, // placeholder; will be patched
1354            p3,
1355            p4,
1356            p5,
1357        });
1358
1359        let state = self
1360            .labels
1361            .get_mut(usize::try_from(label.0).expect("label fits usize"))
1362            .expect("label must exist");
1363
1364        match state {
1365            LabelState::Unresolved(refs) => refs.push(addr),
1366            LabelState::Resolved(target) => {
1367                // Label already resolved; patch immediately.
1368                self.ops[addr].p2 = *target;
1369            }
1370        }
1371
1372        addr
1373    }
1374
1375    /// Resolve a label to the current address and patch all forward refs.
1376    pub fn resolve_label(&mut self, label: Label) {
1377        let addr = i32::try_from(self.current_addr()).expect("program too large");
1378        self.resolve_label_to(label, addr);
1379    }
1380
1381    /// Resolve a label to an explicit address (used for some control patterns).
1382    pub fn resolve_label_to(&mut self, label: Label, address: i32) {
1383        let idx = usize::try_from(label.0).expect("label fits usize");
1384        let state = self.labels.get_mut(idx).expect("label must exist");
1385
1386        match state {
1387            LabelState::Unresolved(refs) => {
1388                // Patch all references.
1389                for &ref_addr in refs.iter() {
1390                    self.ops[ref_addr].p2 = address;
1391                }
1392                *state = LabelState::Resolved(address);
1393            }
1394            LabelState::Resolved(_) => {
1395                // Idempotent: resolving twice is allowed as long as it's consistent.
1396                *state = LabelState::Resolved(address);
1397            }
1398        }
1399    }
1400
1401    // ── Register allocation ─────────────────────────────────────────────
1402
1403    /// Allocate a single persistent register.
1404    pub fn alloc_reg(&mut self) -> i32 {
1405        self.regs.alloc_reg()
1406    }
1407
1408    /// Allocate a contiguous block of persistent registers.
1409    pub fn alloc_regs(&mut self, n: i32) -> i32 {
1410        self.regs.alloc_regs(n)
1411    }
1412
1413    /// Allocate a temporary register (reusable).
1414    pub fn alloc_temp(&mut self) -> i32 {
1415        self.regs.alloc_temp()
1416    }
1417
1418    /// Return a temporary register to the pool.
1419    pub fn free_temp(&mut self, reg: i32) {
1420        self.regs.free_temp(reg);
1421    }
1422
1423    /// Total registers allocated (high water mark).
1424    #[must_use]
1425    pub fn register_count(&self) -> i32 {
1426        self.regs.count()
1427    }
1428
1429    // ── Peephole Passes (IMPL-13) ───────────────────────────────────────
1430
1431    /// Fuse `Integer(lit, reg) + ResultRow(reg, 1)` pairs into
1432    /// `FusedLiteralResultRow(lit, reg)` + `Noop`.
1433    ///
1434    /// Rewrites in-place so program counters, jump targets, and the label
1435    /// tables remain valid without rewiring. The `ResultRow` is replaced by a
1436    /// `Noop` rather than removed so no following instruction shifts.
1437    ///
1438    /// Conservative preconditions per fusion site:
1439    /// - The `Integer`'s target register equals the `ResultRow`'s start
1440    ///   register.
1441    /// - The `ResultRow` emits exactly one column (`p2 == 1`).
1442    /// - The `ResultRow` is NOT a resolved jump target from any prior jump
1443    ///   in this program (a mid-pair jump would otherwise skip the Integer
1444    ///   write and run `ResultRow` against an unrelated register value).
1445    /// - Neither instruction carries a non-`None` P4 payload (Integer/ResultRow
1446    ///   don't use P4 in their canonical form).
1447    /// - Both instructions carry P5 == 0 and P3 == 0.
1448    ///
1449    /// Returns the number of fusions performed.
1450    pub fn apply_fuse_literal_result_row(&mut self) -> usize {
1451        // Collect the set of resolved jump targets. Any address that is the
1452        // target of some jump instruction's `p2` is ineligible to be the
1453        // second half of a fusion pair.
1454        let mut jump_targets: std::collections::HashSet<i32> = std::collections::HashSet::new();
1455        for op in &self.ops {
1456            if op.opcode.is_jump() {
1457                jump_targets.insert(op.p2);
1458            }
1459        }
1460
1461        let mut fused = 0usize;
1462        let len = self.ops.len();
1463        let mut i = 0;
1464        while i + 1 < len {
1465            let is_int = matches!(self.ops[i].opcode, Opcode::Integer)
1466                && self.ops[i].p3 == 0
1467                && self.ops[i].p5 == 0
1468                && matches!(self.ops[i].p4, P4::None);
1469            let is_row = matches!(self.ops[i + 1].opcode, Opcode::ResultRow)
1470                && self.ops[i + 1].p2 == 1
1471                && self.ops[i + 1].p3 == 0
1472                && self.ops[i + 1].p5 == 0
1473                && matches!(self.ops[i + 1].p4, P4::None);
1474            let same_reg = is_int && is_row && self.ops[i].p2 == self.ops[i + 1].p1;
1475            let row_addr = i32::try_from(i + 1).ok();
1476            let row_is_target = row_addr.is_some_and(|a| jump_targets.contains(&a));
1477
1478            if same_reg && !row_is_target {
1479                let lit = self.ops[i].p1;
1480                let reg = self.ops[i].p2;
1481                self.ops[i] = VdbeOp {
1482                    opcode: Opcode::FusedLiteralResultRow,
1483                    p1: lit,
1484                    p2: reg,
1485                    p3: 0,
1486                    p4: P4::None,
1487                    p5: 0,
1488                };
1489                self.ops[i + 1] = VdbeOp {
1490                    opcode: Opcode::Noop,
1491                    p1: 0,
1492                    p2: 0,
1493                    p3: 0,
1494                    p4: P4::None,
1495                    p5: 0,
1496                };
1497                fused += 1;
1498                i += 2;
1499            } else {
1500                i += 1;
1501            }
1502        }
1503        fused
1504    }
1505
1506    // ── Finalization ────────────────────────────────────────────────────
1507
1508    /// Validate all labels are resolved and return the finished program.
1509    pub fn finish(self) -> Result<VdbeProgram> {
1510        // Check for unresolved labels.
1511        for (i, state) in self.labels.iter().enumerate() {
1512            if let LabelState::Unresolved(refs) = state
1513                && !refs.is_empty()
1514            {
1515                return Err(FrankenError::Internal(format!(
1516                    "unresolved label {i} referenced by {} instruction(s)",
1517                    refs.len()
1518                )));
1519            }
1520        }
1521
1522        Ok(VdbeProgram {
1523            ops: self.ops,
1524            register_count: self.regs.count(),
1525        })
1526    }
1527}
1528
1529impl Default for ProgramBuilder {
1530    fn default() -> Self {
1531        Self::new()
1532    }
1533}
1534
1535/// A finalized VDBE bytecode program ready for execution.
1536#[derive(Debug, Clone, PartialEq)]
1537pub struct VdbeProgram {
1538    /// The instruction sequence.
1539    ops: SmallVec<[VdbeOp; 64]>,
1540    /// Number of registers needed (high water mark from allocation).
1541    register_count: i32,
1542}
1543
1544impl VdbeProgram {
1545    /// The instruction sequence.
1546    #[must_use]
1547    pub fn ops(&self) -> &[VdbeOp] {
1548        &self.ops
1549    }
1550
1551    /// Number of instructions.
1552    #[must_use]
1553    pub fn len(&self) -> usize {
1554        self.ops.len()
1555    }
1556
1557    /// Whether the program is empty.
1558    #[must_use]
1559    pub fn is_empty(&self) -> bool {
1560        self.ops.is_empty()
1561    }
1562
1563    /// Number of registers required.
1564    #[must_use]
1565    pub fn register_count(&self) -> i32 {
1566        self.register_count
1567    }
1568
1569    /// Get the instruction at the given program counter.
1570    #[must_use]
1571    pub fn get(&self, pc: usize) -> Option<&VdbeOp> {
1572        self.ops.get(pc)
1573    }
1574
1575    /// Disassemble the program to a human-readable string.
1576    ///
1577    /// Output format matches SQLite's `EXPLAIN` output.
1578    #[must_use]
1579    pub fn disassemble(&self) -> String {
1580        use std::fmt::Write;
1581
1582        let mut out = std::string::String::with_capacity(self.ops.len() * 60);
1583        out.push_str("addr  opcode           p1    p2    p3    p4                 p5\n");
1584        out.push_str("----  ---------------  ----  ----  ----  -----------------  --\n");
1585
1586        for (addr, op) in self.ops.iter().enumerate() {
1587            let p4_str = match &op.p4 {
1588                P4::None => String::new(),
1589                P4::Int(v) => format!("(int){v}"),
1590                P4::Int64(v) => format!("(i64){v}"),
1591                P4::Real(v) => format!("(real){v}"),
1592                P4::Str(s) => format!("(str){s}"),
1593                P4::Blob(b) => format!("(blob)[{}B]", b.len()),
1594                P4::Collation(c) => format!("(coll){c}"),
1595                P4::FuncName(f) => format!("(func){f}"),
1596                P4::FuncNameCollated(f, c) => format!("(func){f} coll={c}"),
1597                P4::Table(t) => format!("(tbl){t}"),
1598                P4::Index(i) => format!("(idx){i}"),
1599                P4::Affinity(a) => format!("(aff){a}"),
1600                P4::PrecomputedHeader(header) => format!("(hdr)[{}B]", header.template.len()),
1601                P4::TimeTravelCommitSeq(seq) => format!("(tt-seq){seq}"),
1602                P4::TimeTravelTimestamp(ts) => format!("(tt-ts){ts}"),
1603            };
1604
1605            writeln!(
1606                &mut out,
1607                "{addr:<4}  {:<15}  {:<4}  {:<4}  {:<4}  {:<17}  {:<2}",
1608                op.opcode.name(),
1609                op.p1,
1610                op.p2,
1611                op.p3,
1612                p4_str,
1613                op.p5,
1614            )
1615            .expect("write to string");
1616        }
1617
1618        out
1619    }
1620}
1621
1622#[cfg(test)]
1623#[allow(clippy::approx_constant)]
1624mod tests {
1625    use super::*;
1626    use std::collections::HashSet;
1627
1628    #[test]
1629    fn opcode_count() {
1630        // COUNT is the exclusive upper bound on discriminants (1..COUNT), so the
1631        // number of opcodes actually defined is COUNT - 1.
1632        assert_eq!(Opcode::COUNT, 200);
1633        assert_eq!(Opcode::COUNT - 1, 199);
1634    }
1635
1636    #[test]
1637    fn opcode_name_roundtrip() {
1638        // Spot check a few opcodes
1639        assert_eq!(Opcode::Goto.name(), "Goto");
1640        assert_eq!(Opcode::Halt.name(), "Halt");
1641        assert_eq!(Opcode::Insert.name(), "Insert");
1642        assert_eq!(Opcode::Delete.name(), "Delete");
1643        assert_eq!(Opcode::ResultRow.name(), "ResultRow");
1644        assert_eq!(Opcode::Noop.name(), "Noop");
1645    }
1646
1647    #[test]
1648    fn opcode_from_byte() {
1649        assert_eq!(Opcode::from_byte(0), None);
1650        assert_eq!(Opcode::from_byte(1), Some(Opcode::Goto));
1651        assert_eq!(Opcode::from_byte(8), Some(Opcode::Halt));
1652        assert_eq!(Opcode::from_byte(190), Some(Opcode::SetSnapshot));
1653        assert_eq!(Opcode::from_byte(191), Some(Opcode::Noop));
1654        assert_eq!(Opcode::from_byte(192), Some(Opcode::LikeConstFast));
1655        assert_eq!(Opcode::from_byte(196), Some(Opcode::FusedLiteralResultRow));
1656        assert_eq!(Opcode::from_byte(197), Some(Opcode::ColumnSubstrPrefix));
1657        assert_eq!(Opcode::from_byte(198), Some(Opcode::ColumnOctetLength));
1658        assert_eq!(Opcode::from_byte(199), Some(Opcode::IfConflictSkip));
1659        assert_eq!(Opcode::from_byte(200), None);
1660        assert_eq!(Opcode::from_byte(255), None);
1661    }
1662
1663    #[test]
1664    fn opcode_from_byte_exhaustive() {
1665        // Every assigned opcode byte should produce Some.
1666        for i in 1..Opcode::COUNT as u8 {
1667            assert!(
1668                Opcode::from_byte(i).is_some(),
1669                "from_byte({i}) returned None"
1670            );
1671        }
1672    }
1673
1674    #[test]
1675    fn test_opcode_distinct_u8_values() {
1676        let mut encoded = HashSet::new();
1677        for byte in 1..Opcode::COUNT as u8 {
1678            let opcode = Opcode::from_byte(byte).expect("opcode byte must decode");
1679            let inserted = encoded.insert(opcode as u8);
1680            assert!(inserted, "duplicate opcode byte value for {:?}", opcode);
1681        }
1682
1683        assert_eq!(
1684            encoded.len(),
1685            Opcode::COUNT - 1,
1686            "every opcode must map to a unique byte"
1687        );
1688    }
1689
1690    #[test]
1691    fn opcode_display() {
1692        assert_eq!(Opcode::Goto.to_string(), "Goto");
1693        assert_eq!(Opcode::Init.to_string(), "Init");
1694    }
1695
1696    #[test]
1697    fn opcode_is_jump() {
1698        assert!(Opcode::Goto.is_jump());
1699        assert!(Opcode::If.is_jump());
1700        assert!(Opcode::IfNot.is_jump());
1701        assert!(Opcode::Eq.is_jump());
1702        assert!(Opcode::Next.is_jump());
1703        assert!(Opcode::Rewind.is_jump());
1704        assert!(Opcode::Init.is_jump());
1705
1706        assert!(!Opcode::Integer.is_jump());
1707        assert!(!Opcode::Add.is_jump());
1708        assert!(!Opcode::Insert.is_jump());
1709        assert!(!Opcode::Noop.is_jump());
1710        assert!(!Opcode::ResultRow.is_jump());
1711    }
1712
1713    #[test]
1714    fn vdbe_op_basic() {
1715        let op = VdbeOp {
1716            opcode: Opcode::Integer,
1717            p1: 42,
1718            p2: 1,
1719            p3: 0,
1720            p4: P4::None,
1721            p5: 0,
1722        };
1723        assert_eq!(op.opcode, Opcode::Integer);
1724        assert_eq!(op.p1, 42);
1725    }
1726
1727    #[test]
1728    fn p4_variants() {
1729        let p4 = P4::Int(42);
1730        assert_eq!(p4, P4::Int(42));
1731
1732        let p4 = P4::Str("hello".to_owned());
1733        assert_eq!(p4, P4::Str("hello".to_owned()));
1734
1735        let p4 = P4::Real(3.14);
1736        assert_eq!(p4, P4::Real(3.14));
1737    }
1738}