ExSQL. Table
(exsql v0.2.1)
Copy Markdown
In-memory table storage.
Rows live in a map keyed by rowid, mirroring SQLite's model where every
table is a B-tree keyed by a 64-bit rowid. Each row is stored as a
positional tuple in columns order (~63% smaller than a per-row
key-bearing map); the executor's hot single-table scan reads columns by
position via the frame's column_index, while other call sites widen a row
back to a key => value map at the read boundary (scan/1, fetch_row/2).
A single INTEGER PRIMARY KEY column is detected as the rowid alias, with
SQLite's semantics: inserting NULL into it auto-assigns the next rowid, and
the column's value is the row's key.
All functions are pure — they return an updated table or an error tuple. Constraint enforcement (NOT NULL, UNIQUE, PRIMARY KEY) happens here, by scanning; real indexes can replace the scans later without changing the interface.
Summary
Types
A CHECK constraint: {constraint_name | nil, expr}.
A composite PK or UNIQUE constraint: {constraint_name | nil, [column_key]}.
A table-level FK: {child_keys, parent_table, parent_keys, actions}, where
actions is %{on_delete: action, on_update: action, deferred: boolean}.
Functions
Reads a positional row tuple at index. An out-of-range index reads as nil,
matching the old sparse-map behavior for columns a given row never stored.
Reads a column from a positional row by key (resolves the index via the table).
Returns the ColumnDef for name, or nil.
Column key → positional index, in columns order (matches a positional row tuple). Cached.
Deletes the rows with the given rowids.
Fetches one stored row as a key => value map, or :error.
Like fetch_row/2 but raises if the rowid is absent.
The {key, name, affinity, collate} tuples used to build a query frame.
Returns the cached value, recomputing if the cache was invalidated by a
column mutation.
Reads one stored row as a map, or nil if absent.
Inserts a row given as a map of column key => value (already evaluated and affinity-coerced by the executor). Missing columns get their DEFAULT or NULL.
Case-insensitive column lookup key, since SQL identifiers fold case.
Re-narrows all stored rows — currently held as key => value maps — to
positional tuples against the table's current columns. Used after an
ALTER TABLE rebuilds rows as maps under a changed column layout.
Creates a table from a name, parsed column definitions, and optional table-level constraints.
Builds a positional row tuple from a key => value map (missing keys → nil).
Rebuilds a key => value map from a positional row tuple (compat for un-migrated callers).
All rows in rowid order, as {rowid, row} pairs.
Replaces the row at rowid with row (a full row map), re-checking
constraints. If the rowid-alias column changed, the row is re-keyed.
Takes the same :on_conflict option as insert/3.
Types
A CHECK constraint: {constraint_name | nil, expr}.
A composite PK or UNIQUE constraint: {constraint_name | nil, [column_key]}.
@type foreign_key() :: {[String.t()], String.t(), [String.t()], ExSQL.AST.CreateTable.fk_actions()}
A table-level FK: {child_keys, parent_table, parent_keys, actions}, where
actions is %{on_delete: action, on_update: action, deferred: boolean}.
@type row() :: %{required(String.t()) => ExSQL.Value.t()}
@type t() :: %ExSQL.Table{ autoincrement: boolean(), autoindexes: [index()], checks: [check_constraint()], column_index: term(), columns: [ExSQL.AST.ColumnDef.t()], composite_keys: [composite_constraint()], composite_uniques: [composite_constraint()], foreign_keys: [foreign_key()], frame_columns: term(), indexes: [index()], name: String.t(), next_rowid: pos_integer(), rowid_alias: String.t() | nil, rows: %{required(integer()) => row()}, schema: String.t() | nil, sequence: integer(), sequence_row: boolean(), strict: boolean(), without_rowid: boolean() }
Functions
@spec cell(tuple(), non_neg_integer()) :: ExSQL.Value.t()
Reads a positional row tuple at index. An out-of-range index reads as nil,
matching the old sparse-map behavior for columns a given row never stored.
@spec cell(t(), tuple(), String.t()) :: ExSQL.Value.t()
Reads a column from a positional row by key (resolves the index via the table).
@spec column(t(), String.t()) :: ExSQL.AST.ColumnDef.t() | nil
Returns the ColumnDef for name, or nil.
@spec column_index(t()) :: %{required(String.t()) => non_neg_integer()}
Column key → positional index, in columns order (matches a positional row tuple). Cached.
Deletes the rows with the given rowids.
Fetches one stored row as a key => value map, or :error.
Like fetch_row/2 but raises if the rowid is absent.
The {key, name, affinity, collate} tuples used to build a query frame.
Returns the cached value, recomputing if the cache was invalidated by a
column mutation.
Reads one stored row as a map, or nil if absent.
Inserts a row given as a map of column key => value (already evaluated and affinity-coerced by the executor). Missing columns get their DEFAULT or NULL.
Options:
:rowid— an explicit rowid (from inserting intorowidby name):on_conflict—:abort(default) errors,:replacedeletes the conflicting rows first,:ignoreskips the insert and returns:ignore
Case-insensitive column lookup key, since SQL identifiers fold case.
Re-narrows all stored rows — currently held as key => value maps — to
positional tuples against the table's current columns. Used after an
ALTER TABLE rebuilds rows as maps under a changed column layout.
@spec new(String.t(), [ExSQL.AST.ColumnDef.t()], keyword()) :: t()
Creates a table from a name, parsed column definitions, and optional table-level constraints.
Builds a positional row tuple from a key => value map (missing keys → nil).
Rebuilds a key => value map from a positional row tuple (compat for un-migrated callers).
All rows in rowid order, as {rowid, row} pairs.
Like scan/1 but yields rows as raw positional tuples (no widening to maps).
The hot single-table scan reads columns positionally via the frame's
column_index, skipping the per-row map build that scan/1 does.
Replaces the row at rowid with row (a full row map), re-checking
constraints. If the rowid-alias column changed, the row is re-keyed.
Takes the same :on_conflict option as insert/3.