ExSQL. Vdbe
(exsql v0.1.5)
Copy Markdown
A small register virtual machine, in the spirit of SQLite's VDBE
(vdbe.c). ExSQL.Executor walks the AST directly; for the common
single-table scan/filter/project shape it instead compiles the statement to
a flat opcode program (see ExSQL.Executor.compile_vdbe/3) and runs it here,
avoiding the per-row environment construction and recursive expression
descent of the tree walker.
A program is a tuple of opcodes addressed by program counter. State threads through a single tail-recursive loop:
- one cursor over the table's rows (
Rewind/Nextadvance it), - an integer-keyed register file the opcodes read and write,
- the emitted output rows plus LIMIT/OFFSET counters.
Opcodes (each a tuple tagged by its name):
{:rewind, end_pc}— position the cursor on the first row, or jump toend_pcwhen the table is empty.{:next, top_pc}— advance the cursor and jump totop_pc, or fall through when exhausted.{:column, col_key, reg}— load the cursor row's column intoreg.{:rowid, reg}— load the cursor row's rowid intoreg.{:value, value, reg}— load a constant intoreg.{:eval, fun, reg}— loadfun.(row, rowid)intoreg. The closure is built by the compiler over the tree walker's evaluator, so arbitrary projection/sort expressions reuse exact SQL semantics.{:cmp, op, reg_a, aff_a, reg_b, aff_b, collation, fail_pc}— apply comparison affinity then compare; jump tofail_pcunless the result is true (NULL and false both fail, as in a WHERE clause).{:filter, fun, fail_pc}— jump tofail_pcunlessfun.(row, rowid)is true; the general WHERE form when the:cmpchain doesn't apply.{:result_row, proj_regs, key_regs}— emit a row fromproj_regs. With no ORDER BY (key_regs == []) rows stream out honoring OFFSET/LIMIT; with ORDER BY every row is collected with itskey_regssort key, then sorted and clamped after the scan.{:halt}— stop.
The comparison opcodes delegate to ExSQL.Value, the same module the tree
walker uses, so affinity, collation, and NULL semantics match exactly.
Summary
Functions
Runs program over rows (a [{rowid, row_map}] cursor, in scan order).
Types
@type program() :: tuple()
An opcode program addressed by program counter.
Functions
@spec run( [{integer(), map()}], program(), non_neg_integer() | nil, non_neg_integer() | nil, [:asc | :desc] | nil ) :: [[ExSQL.Value.t()]]
Runs program over rows (a [{rowid, row_map}] cursor, in scan order).
order, when non-nil, is a list of :asc | :desc directions aligned with the
key_regs emitted by result_row — the rows are collected, sorted by their
keys (NULL sorts low, as in SQLite), then OFFSET/LIMIT applied. With order == nil rows stream out and LIMIT can stop the scan early.