Current section
Files
Jump to
Current section
Files
lib/xqlite.ex
defmodule Xqlite do
@moduledoc ~S"""
This is the central module of this library. All SQLite operations can be performed from here.
Note that they delegate to other modules which you can also use directly.
"""
@type conn :: reference()
@type error :: {:error, any()}
@spec int2bool(0 | 1) :: true | false
def int2bool(0), do: false
def int2bool(1), do: true
@doc """
Enables strict mode only for the lifetime of the given database connection.
In strict mode, SQLite is less forgiving. For example, an attempt to insert
a string into an INTEGER column of a `STRICT` table will result in an error,
whereas in normal mode it might be coerced or stored as text.
This setting only affects tables declared with the `STRICT` keyword.
See: [STRICT Tables](https://www.sqlite.org/stricttables.html)
"""
@spec enable_strict_mode(conn()) :: :ok | error()
def enable_strict_mode(conn) do
XqliteNIF.set_pragma(conn, "strict", :on)
end
@doc """
Disables strict mode only for the lifetime given database connection (SQLite's default).
See `enable_strict_mode/1` for details.
"""
@spec disable_strict_mode(conn()) :: :ok | error()
def disable_strict_mode(conn) do
XqliteNIF.set_pragma(conn, "strict", :off)
end
@doc """
Enables foreign key constraint enforcement for the given database connection.
By default, SQLite parses foreign key constraints but does not enforce them.
This function turns on enforcement.
See: [SQLite PRAGMA foreign_keys](https://www.sqlite.org/pragma.html#pragma_foreign_keys)
"""
@spec enable_foreign_key_enforcement(conn()) :: :ok | error()
def enable_foreign_key_enforcement(conn) do
XqliteNIF.set_pragma(conn, "foreign_keys", :on)
end
@doc """
Disables foreign key constraint enforcement for the given database connection (default behavior).
See `enable_foreign_key_enforcement/1` for details.
"""
@spec disable_foreign_key_enforcement(conn()) :: :ok | error()
def disable_foreign_key_enforcement(conn) do
XqliteNIF.set_pragma(conn, "foreign_keys", :off)
end
@doc """
Creates a stream that executes a query and emits rows as maps.
This provides a high-level, idiomatic Elixir `Stream` for processing large
result sets without loading them all into memory at once. Rows are fetched
from the database in batches as the stream is consumed.
## Options
* `:batch_size` (integer, default: `500`) - The maximum number of rows
to fetch from the database in a single batch.
## Examples
iex> {:ok, conn} = XqliteNIF.open_in_memory()
iex> XqliteNIF.execute_batch(conn, "CREATE TABLE users(id, name); INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');")
:ok
iex> Xqlite.stream(conn, "SELECT id, name FROM users;") |> Enum.to_list()
[%{id: 1, name: "Alice"}, %{id: 2, name: "Bob"}]
If the underlying query preparation or initial NIF stream setup fails, this
function will return an `{:error, reason}` tuple directly instead of a stream.
Errors that occur during stream consumption (e.g., database connection lost
mid-stream) will be logged and will cause the stream to halt.
"""
@spec stream(conn(), String.t(), list() | keyword(), keyword()) ::
Enumerable.t() | {:error, Xqlite.error()}
def stream(conn, sql, params \\ [], opts \\ []) do
start_fun = &Xqlite.StreamResourceCallbacks.start_fun/1
next_fun = &Xqlite.StreamResourceCallbacks.next_fun/1
after_fun = &Xqlite.StreamResourceCallbacks.after_fun/1
# `Stream.resource/3` expects the start_fun to return {:ok, acc} or {:error, reason}.
# If it returns {:error, reason}, Stream.resource will raise an error.
# To align with our spec of returning {:error, reason} directly, we must
# call start_fun ourselves first.
case start_fun.({conn, sql, params, opts}) do
{:ok, acc} ->
# If setup is successful, build the stream resource.
# The start function for Stream.resource now just returns the successful acc.
Stream.resource(fn -> acc end, next_fun, after_fun)
{:error, _reason} = error ->
# If setup fails, return the error tuple directly.
error
end
end
end