ExSQL.Database (exsql v0.1.5)

Copy Markdown

An immutable in-memory database: a schema of named tables.

This plays the role of SQLite's connection + pager state, except every operation returns a new database value. Use it directly for a purely functional workflow, or hold it in an ExSQL.Connection process for a stateful, sqlite3-like handle.

{:ok, _, db} = ExSQL.Executor.run(ExSQL.Database.new(), "CREATE TABLE t (a)")

Summary

Types

A connection-local aggregate callback. It receives a list of evaluated, non-NULL argument rows and must return a SQL value, {:ok, value}, or {:error, message}. Incremental window callbacks use the same registry but store a callback map and kind: :incremental_window.

A connection-local collation callback. It receives two TEXT values and returns :lt/:eq/:gt, a negative/zero/positive integer, or {:ok, result} / {:error, message}.

A materialized CTE result, threaded through query execution.

A connection-local scalar function callback. It receives evaluated SQL values and must return a SQL value, {:ok, value}, or {:error, message}.

t()

A stored trigger: parsed definition plus a creation sequence number that fixes firing order.

Open transactions and savepoints, newest first. Each entry snapshots both tables and views as they were when it was opened — rollback restores both.

A stored view: the parsed query and an optional explicit column list.

Functions

Returns true if any arity is registered for this aggregate function name.

Registers or replaces a connection-local aggregate function.

Registers or replaces a connection-local collation.

Registers or replaces a connection-local incremental aggregate window function.

Registers or replaces a connection-local scalar function.

Adds a new table; errors if the name collides with a table or existing view.

Adds a new view; errors if the name collides with a table or existing view.

Registers or replaces a connection-local aggregate window function.

Removes attached schema header state.

Removes a table; errors if it does not exist or if the name is a view.

Removes a view; errors if it does not exist or if the name is a table.

Fetches a registered aggregate function by name and exact SQL arity.

Fetches a registered collation by name.

Fetches a registered scalar function by name and exact arity.

Fetches a table by name (case-insensitive).

Fetches a view by name (case-insensitive).

Finds which table owns the index with the given name, or nil. Index lookup is database-global and case-insensitive.

Finds which table owns the index with the given name within a schema, or nil.

Returns true if any table has an index with this name.

Returns true if a table in the schema has an index with this name.

Fetches a table by unqualified name using SQLite's schema lookup order.

Fetches a view by unqualified name using SQLite's schema lookup order.

Returns an empty database.

Stores a schema header value for main or an attached schema.

Replaces a table's state after DML.

Records the row-count state exposed by changes() and total_changes().

Restores schema from a snapshot.

Returns true if any arity is registered for this scalar function name.

Bumps the schema cookie using SQLite's signed 32-bit wraparound.

Returns a schema header value for main or an attached schema.

Returns a snapshot of mutable schema state (tables, views, triggers) for transactions.

Internal storage key for schema-qualified tables.

Types

aggregate_function()

@type aggregate_function() :: %{
  :name => String.t(),
  :arity => non_neg_integer(),
  :callback => function() | map(),
  optional(:kind) => :frame | :incremental_window
}

A connection-local aggregate callback. It receives a list of evaluated, non-NULL argument rows and must return a SQL value, {:ok, value}, or {:error, message}. Incremental window callbacks use the same registry but store a callback map and kind: :incremental_window.

collation()

@type collation() :: %{name: String.t(), callback: function()}

A connection-local collation callback. It receives two TEXT values and returns :lt/:eq/:gt, a negative/zero/positive integer, or {:ok, result} / {:error, message}.

cte()

@type cte() :: %{columns: [String.t()], rows: [[term()]], affinities: [atom()]}

A materialized CTE result, threaded through query execution.

scalar_function()

@type scalar_function() :: %{
  name: String.t(),
  arity: non_neg_integer(),
  callback: function()
}

A connection-local scalar function callback. It receives evaluated SQL values and must return a SQL value, {:ok, value}, or {:error, message}.

t()

@type t() :: %ExSQL.Database{
  active_triggers: [String.t()],
  aggregate_functions: %{
    required({String.t(), non_neg_integer()}) => aggregate_function()
  },
  analysis_limit: non_neg_integer(),
  application_id: integer(),
  attached_databases: [
    %{seq: pos_integer(), name: String.t(), file: String.t()}
  ],
  auto_vacuum: 0..2,
  automatic_index: boolean(),
  busy_timeout: non_neg_integer(),
  cache_size: integer(),
  cache_spill: non_neg_integer(),
  case_sensitive_like: boolean(),
  cell_size_check: boolean(),
  changes: non_neg_integer(),
  checkpoint_fullfsync: boolean(),
  collations: %{required(String.t()) => collation()},
  count_changes: boolean(),
  ctes: %{required(String.t()) => cte()},
  default_cache_size: non_neg_integer(),
  defer_foreign_keys: boolean(),
  empty_result_callbacks: boolean(),
  foreign_keys: boolean(),
  full_column_names: boolean(),
  fullfsync: boolean(),
  ignore_check_constraints: boolean(),
  journal_mode: String.t(),
  journal_size_limit: integer(),
  last_insert_rowid: integer(),
  locking_mode: String.t(),
  max_page_count: pos_integer(),
  page_size: pos_integer(),
  page_size_locked: boolean(),
  pending_ctes: term(),
  query_only: boolean(),
  read_uncommitted: boolean(),
  recursive_triggers: boolean(),
  reverse_unordered_selects: boolean(),
  scalar_functions: %{
    required({String.t(), non_neg_integer()}) => scalar_function()
  },
  schema_headers: %{required(String.t()) => map()},
  schema_version: integer(),
  secure_delete: 0..2,
  short_column_names: boolean(),
  soft_heap_limit: non_neg_integer(),
  sqlite_sequence_orphans: map(),
  synchronous: 0..3,
  tables: %{required(String.t()) => ExSQL.Table.t()},
  temp_store: 0..2,
  threads: 0..8,
  total_changes: non_neg_integer(),
  triggers: %{required(String.t()) => trigger()},
  trusted_schema: boolean(),
  txn_stack: [txn_entry()],
  user_version: integer(),
  views: %{required(String.t()) => view()},
  wal_autocheckpoint: non_neg_integer()
}

trigger()

@type trigger() :: %{
  key: String.t(),
  name: String.t(),
  schema: String.t() | nil,
  table_schema: String.t() | nil,
  table_key: String.t(),
  timing: :before | :after | :instead_of,
  event: :insert | :delete | :update,
  update_columns: [String.t()] | nil,
  when: term() | nil,
  body: [term()],
  seq: non_neg_integer()
}

A stored trigger: parsed definition plus a creation sequence number that fixes firing order.

txn_entry()

@type txn_entry() ::
  {:begin | {:savepoint, String.t()},
   %{
     tables: %{required(String.t()) => ExSQL.Table.t()},
     views: %{required(String.t()) => view()}
   }}

Open transactions and savepoints, newest first. Each entry snapshots both tables and views as they were when it was opened — rollback restores both.

view()

@type view() :: %{
  name: String.t(),
  schema: String.t() | nil,
  columns: [String.t()] | nil,
  query: term()
}

A stored view: the parsed query and an optional explicit column list.

Functions

aggregate_function_exists?(db, name)

@spec aggregate_function_exists?(t(), String.t()) :: boolean()

Returns true if any arity is registered for this aggregate function name.

create_aggregate_function(db, name, arity, callback)

@spec create_aggregate_function(t(), String.t(), non_neg_integer(), function()) ::
  {:ok, t()} | {:error, String.t()}

Registers or replaces a connection-local aggregate function.

The callback receives one list argument containing evaluated, non-NULL argument rows. A one-argument SQL aggregate gets rows like [[value], ...]; a two-argument aggregate gets rows like [[a, b], ...].

create_collation(db, name, callback)

@spec create_collation(t(), String.t(), function()) ::
  {:ok, t()} | {:error, String.t()}

Registers or replaces a connection-local collation.

The callback receives two TEXT values and returns :lt/:eq/:gt, a negative/zero/positive integer, or {:ok, result} / {:error, message}.

create_incremental_window_function(db, name, arity, callbacks)

@spec create_incremental_window_function(t(), String.t(), non_neg_integer(), map()) ::
  {:ok, t()} | {:error, String.t()}

Registers or replaces a connection-local incremental aggregate window function.

Callback map keys follow SQLite's aggregate window model: :init (arity 0), :step and :inverse (arity 2, receiving state and evaluated SQL argument list), and :value / :final (arity 1).

create_scalar_function(db, name, arity, callback)

@spec create_scalar_function(t(), String.t(), non_neg_integer(), function()) ::
  {:ok, t()} | {:error, String.t()}

Registers or replaces a connection-local scalar function.

The callback arity must match the SQL arity exactly. The callback receives evaluated SQL values as positional arguments and may return a SQL value, {:ok, value}, or {:error, message}.

create_table(db, table)

@spec create_table(t(), ExSQL.Table.t()) :: {:ok, t()} | {:error, String.t()}

Adds a new table; errors if the name collides with a table or existing view.

create_view(db, view)

@spec create_view(t(), view()) :: {:ok, t()} | {:error, String.t()}

Adds a new view; errors if the name collides with a table or existing view.

create_window_function(db, name, arity, callback)

@spec create_window_function(t(), String.t(), non_neg_integer(), function()) ::
  {:ok, t()} | {:error, String.t()}

Registers or replaces a connection-local aggregate window function.

The callback receives one list argument containing evaluated, non-NULL argument rows from the current window frame.

drop_schema_header(db, schema)

@spec drop_schema_header(t(), String.t()) :: t()

Removes attached schema header state.

drop_table(db, name)

@spec drop_table(t(), String.t()) :: {:ok, t()} | {:error, String.t()}

Removes a table; errors if it does not exist or if the name is a view.

drop_table(db, schema, name)

@spec drop_table(t(), String.t() | nil, String.t()) ::
  {:ok, t()} | {:error, String.t()}

drop_view(db, name)

@spec drop_view(t(), String.t()) :: {:ok, t()} | {:error, String.t()}

Removes a view; errors if it does not exist or if the name is a table.

drop_view(db, schema, name)

@spec drop_view(t(), String.t() | nil, String.t()) ::
  {:ok, t()} | {:error, String.t()}

fetch_aggregate_function(db, name, arity)

@spec fetch_aggregate_function(t(), String.t(), non_neg_integer()) ::
  {:ok, aggregate_function()} | :error

Fetches a registered aggregate function by name and exact SQL arity.

fetch_collation(db, name)

@spec fetch_collation(t(), String.t()) :: {:ok, collation()} | :error

Fetches a registered collation by name.

fetch_scalar_function(db, name, arity)

@spec fetch_scalar_function(t(), String.t(), non_neg_integer()) ::
  {:ok, scalar_function()} | :error

Fetches a registered scalar function by name and exact arity.

fetch_table(db, name)

@spec fetch_table(t(), String.t()) :: {:ok, ExSQL.Table.t()} | {:error, String.t()}

Fetches a table by name (case-insensitive).

fetch_view(db, name)

@spec fetch_view(t(), String.t()) :: {:ok, view()} | :error

Fetches a view by name (case-insensitive).

fetch_view(db, schema, name)

@spec fetch_view(t(), String.t() | nil, String.t()) :: {:ok, view()} | :error

find_index_owner(db, index_name)

@spec find_index_owner(t(), String.t()) :: {ExSQL.Table.t(), map()} | nil

Finds which table owns the index with the given name, or nil. Index lookup is database-global and case-insensitive.

find_index_owner(db, schema, index_name)

@spec find_index_owner(t(), String.t() | nil, String.t()) ::
  {ExSQL.Table.t(), map()} | nil

Finds which table owns the index with the given name within a schema, or nil.

index_exists?(db, index_name)

@spec index_exists?(t(), String.t()) :: boolean()

Returns true if any table has an index with this name.

index_exists?(db, schema, index_name)

@spec index_exists?(t(), String.t() | nil, String.t()) :: boolean()

Returns true if a table in the schema has an index with this name.

lookup_table(db, name)

@spec lookup_table(t(), String.t()) :: {:ok, ExSQL.Table.t()} | {:error, String.t()}

Fetches a table by unqualified name using SQLite's schema lookup order.

lookup_view(db, name)

@spec lookup_view(t(), String.t()) :: {:ok, view()} | :error

Fetches a view by unqualified name using SQLite's schema lookup order.

new()

@spec new() :: t()

Returns an empty database.

put_schema_header_value(db, schema, field, value)

@spec put_schema_header_value(
  t(),
  String.t() | nil,
  :schema_version | :user_version | :application_id,
  integer()
) :: t()

Stores a schema header value for main or an attached schema.

put_table(db, table)

@spec put_table(t(), ExSQL.Table.t()) :: t()

Replaces a table's state after DML.

record_changes(db, count)

@spec record_changes(t(), non_neg_integer()) :: t()

Records the row-count state exposed by changes() and total_changes().

record_changes(db, count, last_insert_rowid)

@spec record_changes(t(), non_neg_integer(), integer() | nil) :: t()

restore_schema(db, snapshot)

@spec restore_schema(t(), map()) :: t()

Restores schema from a snapshot.

scalar_function_exists?(db, name)

@spec scalar_function_exists?(t(), String.t()) :: boolean()

Returns true if any arity is registered for this scalar function name.

schema_changed(db)

@spec schema_changed(t()) :: t()

Bumps the schema cookie using SQLite's signed 32-bit wraparound.

schema_changed(db, schema)

@spec schema_changed(t(), String.t() | nil) :: t()

schema_header_value(db, schema, field)

@spec schema_header_value(
  t(),
  String.t() | nil,
  :schema_version | :user_version | :application_id
) :: integer()

Returns a schema header value for main or an attached schema.

schema_snapshot(db)

@spec schema_snapshot(t()) :: %{
  tables: map(),
  views: map(),
  triggers: map(),
  sqlite_sequence_orphans: map(),
  schema_version: integer(),
  user_version: integer(),
  application_id: integer(),
  schema_headers: map()
}

Returns a snapshot of mutable schema state (tables, views, triggers) for transactions.

table_storage_key(schema, name)

@spec table_storage_key(String.t() | nil, String.t()) :: String.t()

Internal storage key for schema-qualified tables.