Electric.ShapeCache.ShapeStatus.ShapeDb.Sqlite3 (electric v1.6.2)

Copy Markdown View Source

Drop-in shim over :esqlite3 that mirrors the subset of the Exqlite.Sqlite3 API used by Connection and Query.

Only Connection and Query hold an alias to this module. All other code continues to call those two modules unchanged, so swapping the underlying SQLite NIF is fully contained here.

API mapping

Exqlite.Sqlite3:esqlite3 / notes
open(path, opts)open(uri) – opts encoded as URI params
close(conn)close(conn)
execute(conn, sql)exec(conn, sql)
prepare(conn, sql)prepare(conn, sql)
release(conn, stmt)no-op – GC'd by esqlite
bind(stmt, binds)bind(stmt, binds)
step(conn, stmt)step(stmt) – conn arg dropped
reset(stmt)reset(stmt)
fetch_all(conn, stmt)fetchall(stmt)
changes(conn)
multi_step(conn, stmt)step loop; returns {:rows, rows}/{:done, rows}
enable_load_extension(conn, bool)not supported – always returns error
bind_parameter_count(stmt)column_names heuristic (explain only)

How esqlite manages prepared statements

When prepare is called, the NIF allocates an esqlite3_stmt resource, immediately calls enif_release_resource to drop the C-side reference, and returns the resource wrapped in an Erlang term. From that point the BEAM garbage collector is the sole owner: when no Erlang process holds a reference to the term, the GC calls the registered destructor which runs sqlite3_finalize. The NIF also holds an enif_keep_resource reference from the statement back to its connection, ensuring the connection is never finalized before all its statements are. There is no explicit finalize or release call exposed — lifetime is entirely GC- driven.

Hence the release/1 function is a no-op.

Summary

Functions

Bind positional or named parameters to a prepared statement.

Return the number of bind parameters in a prepared statement.

Build a file: URI from a path with the given opts as query params

Return {:ok, n} for the number of rows changed by the last DML statement.

Enable or disable SQLite extension loading.

Execute a raw SQL statement (no results returned).

Fetch all remaining rows from a prepared statement.

Step through a prepared statement in chunks.

Opens a SQLite database.

Release a prepared statement. esqlite relies on GC; this is a no-op.

Step a prepared statement once.

Types

connection()

@type connection() :: :esqlite3.esqlite3()

statement()

@type statement() :: :esqlite3.esqlite3_stmt()

Functions

bind(stmt, binds)

@spec bind(statement(), list()) :: :ok | {:error, term()}

Bind positional or named parameters to a prepared statement.

Accepts the exqlite bind list format including {:blob, value} tagged tuples, plain integers, binaries, and named {"@name", value} pairs.

bind_parameter_count(stmt)

@spec bind_parameter_count(statement()) :: non_neg_integer()

Return the number of bind parameters in a prepared statement.

Used only by the explain/2 diagnostic path. esqlite does not expose sqlite3_bind_parameter_count directly, so we derive it from column names of the statement. For EXPLAIN QUERY PLAN usage the count just needs to be non-negative; we fall back to 0.

build_uri(path, opts)

Build a file: URI from a path with the given opts as query params

See: https://sqlite.org/uri.html#uri_filenames_in_sqlite

Examples

iex> build_uri(":memory:", [])
"file:memory?mode=memory"

iex> build_uri("/my/path/here", [])
"file:/my/path/here?mode=rwc"

iex> build_uri("/my/path/here", mode: :readonly)
"file:/my/path/here?mode=ro"

iex> build_uri("/my/#path?/is-here", mode: :readonly)
"file:/my/%23path%3F/is-here?mode=ro"

iex> build_uri("/my//path//here", mode: :readwrite)
"file:/my/path/here?mode=rwc"

changes(conn)

@spec changes(connection()) :: {:ok, non_neg_integer()}

Return {:ok, n} for the number of rows changed by the last DML statement.

close(conn)

@spec close(connection()) :: :ok | {:error, term()}

enable_load_extension(conn, enable)

@spec enable_load_extension(connection(), boolean()) :: :ok | {:error, :not_supported}

Enable or disable SQLite extension loading.

esqlite does not expose sqlite3_enable_load_extension. Returns {:error, :not_supported} so callers can handle gracefully.

execute(conn, sql)

@spec execute(connection(), String.t()) :: :ok | {:error, term()}

Execute a raw SQL statement (no results returned).

fetch_all(conn, stmt)

@spec fetch_all(connection(), statement()) :: {:ok, [list()]} | {:error, term()}

Fetch all remaining rows from a prepared statement.

multi_step(conn, stmt, chunk_size \\ 50)

Step through a prepared statement in chunks.

Returns {:rows, rows} when there are more rows to fetch, or {:done, rows} when the cursor is exhausted.

The conn argument is accepted for API compatibility but ignored. The chunk size matches exqlite's default (50 rows per call).

open(path, opts \\ [])

@spec open(
  String.t(),
  keyword()
) :: {:ok, connection()} | {:error, term()}

Opens a SQLite database.

opts follows the exqlite convention:

  • [mode: [:readonly, :nomutex]] → opens as file:<path>?mode=ro
  • [] (default) → opens as file:<path>?mode=rwc

The :memory: path is passed through unchanged.

prepare(conn, sql)

@spec prepare(connection(), String.t()) :: {:ok, statement()} | {:error, term()}

release(conn, stmt)

@spec release(connection(), statement()) :: :ok | {:error, term()}

Release a prepared statement. esqlite relies on GC; this is a no-op.

reset(stmt)

@spec reset(statement()) :: :ok | {:error, term()}

step(conn, stmt)

@spec step(connection(), statement()) :: {:row, list()} | :done | {:error, term()}

Step a prepared statement once.

Returns {:row, row} or :done (matching the exqlite contract). The conn argument is accepted for API compatibility but ignored.