# `Electric.ShapeCache.ShapeStatus.ShapeDb.Sqlite3`
[🔗](https://github.com/electric-sql/electric/tree/%40core/sync-service%401.6.2/packages/sync-service/lib/electric/shape_cache/shape_status/shape_db/sqlite3.ex#L1)

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)                       | {:ok, 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.

# `connection`

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

# `statement`

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

# `bind`

```elixir
@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`

```elixir
@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`

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`

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

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

# `close`

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

# `enable_load_extension`

```elixir
@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`

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

Execute a raw SQL statement (no results returned).

# `fetch_all`

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

Fetch all remaining rows from a prepared statement.

# `multi_step`

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`

```elixir
@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`

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

# `release`

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

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

# `reset`

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

# `step`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
