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/Next advance 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 to end_pc when the table is empty.
  • {:next, top_pc} — advance the cursor and jump to top_pc, or fall through when exhausted.
  • {:column, col_key, reg} — load the cursor row's column into reg.
  • {:rowid, reg} — load the cursor row's rowid into reg.
  • {:value, value, reg} — load a constant into reg.
  • {:eval, fun, reg} — load fun.(row, rowid) into reg. 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 to fail_pc unless the result is true (NULL and false both fail, as in a WHERE clause).
  • {:filter, fun, fail_pc} — jump to fail_pc unless fun.(row, rowid) is true; the general WHERE form when the :cmp chain doesn't apply.
  • {:result_row, proj_regs, key_regs} — emit a row from proj_regs. With no ORDER BY (key_regs == []) rows stream out honoring OFFSET/LIMIT; with ORDER BY every row is collected with its key_regs sort 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

Types

An opcode program addressed by program counter.

Functions

Runs program over rows (a [{rowid, row_map}] cursor, in scan order).

Types

program()

@type program() :: tuple()

An opcode program addressed by program counter.

Functions

run(rows, program, limit, offset, order \\ nil)

@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.