Exqlite.Sqlite3 (Exqlite v0.37.0)

Copy Markdown View Source

The interface to the NIF implementation.

Summary

Functions

Resets a prepared statement and binds values to it.

Binds a blob value to a prepared statement.

Binds a float value to a prepared statement.

Binds an integer value to a prepared statement.

Binds a null value to a prepared statement.

Returns number of SQL parameters in a prepared statement.

Binds a text value to a prepared statement.

Cancel a running query: wake any busy handler sleep and interrupt VDBE execution.

Get the number of changes recently.

Closes the database and releases any underlying resources.

Disconnect from database and then reopen as an in-memory database based on the serialized binary.

Allow loading native extensions.

Executes an sql script. Multiple stanzas can be passed at once.

Interrupt a long-running query.

Opens a new sqlite database at the Path provided.

Once finished with the prepared statement, call this to release the underlying resources.

Resets a prepared statement.

Serialize the contents of the database to a binary.

Set an authorizer that denies specific SQL operations.

Set the busy timeout in milliseconds without destroying the custom busy handler.

Send log messages to a process.

Configure how often SQLite invokes the progress handler during statement execution.

Send data change notifications to a process.

Causes the database connection to free as much memory as it can. This is useful if you are on a memory restricted system.

Types

bind_value()

@type bind_value() ::
  NaiveDateTime.t()
  | DateTime.t()
  | Date.t()
  | Time.t()
  | number()
  | iodata()
  | {:blob, iodata()}
  | atom()

db()

@type db() :: reference()

open_mode()

@type open_mode() :: :readwrite | :readonly | :nomutex

open_opt()

@type open_opt() :: {:mode, :readwrite | :readonly | [open_mode()]}

reason()

@type reason() :: atom() | String.t()

row()

@type row() :: list()

statement()

@type statement() :: reference()

Functions

bind(stmt, args)

@spec bind(
  statement(),
  [bind_value()] | %{optional(String.t()) => bind_value()} | nil
) :: :ok

Resets a prepared statement and binds values to it.

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?, ?, ?, ?")
iex> Sqlite3.bind(stmt, [42, 3.14, "Alice", {:blob, <<0, 0, 0>>}, nil])
iex> Sqlite3.step(conn, stmt)
{:row, [42, 3.14, "Alice", <<0, 0, 0>>, nil]}

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT :42, @pi, $name, @blob, :null")
iex> Sqlite3.bind(stmt, %{":42" => 42, "@pi" => 3.14, "$name" => "Alice", :"@blob" => {:blob, <<0, 0, 0>>}, ~c":null" => nil})
iex> Sqlite3.step(conn, stmt)
{:row, [42, 3.14, "Alice", <<0, 0, 0>>, nil]}

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind(stmt, [42, 3.14, "Alice"])
** (ArgumentError) expected 1 arguments, got 3

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?")
iex> Sqlite3.bind(stmt, [42])
** (ArgumentError) expected 2 arguments, got 1

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind(stmt, [:erlang.list_to_pid(~c"<0.0.0>")])
** (ArgumentError) unsupported type: #PID<0.0.0>

bind_blob(stmt, index, blob)

@spec bind_blob(statement(), non_neg_integer(), binary()) :: :ok

Binds a blob value to a prepared statement.

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_blob(stmt, 1, <<0, 0, 0>>)
:ok

bind_float(stmt, index, float)

@spec bind_float(statement(), non_neg_integer(), float()) :: :ok

Binds a float value to a prepared statement.

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_float(stmt, 1, 3.14)
:ok

bind_integer(stmt, index, integer)

@spec bind_integer(statement(), non_neg_integer(), integer()) :: :ok

Binds an integer value to a prepared statement.

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_integer(stmt, 1, 42)
:ok

bind_null(stmt, index)

@spec bind_null(statement(), non_neg_integer()) :: :ok

Binds a null value to a prepared statement.

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_null(stmt, 1)
:ok

bind_parameter_count(stmt)

@spec bind_parameter_count(statement()) :: non_neg_integer() | {:error, reason()}

Returns number of SQL parameters in a prepared statement.

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?, ?")
iex> Sqlite3.bind_parameter_count(stmt)
2

bind_text(stmt, index, text)

@spec bind_text(statement(), non_neg_integer(), String.t()) :: :ok

Binds a text value to a prepared statement.

iex> {:ok, conn} = Sqlite3.open(":memory:", [:readonly])
iex> {:ok, stmt} = Sqlite3.prepare(conn, "SELECT ?")
iex> Sqlite3.bind_text(stmt, 1, "Alice")
:ok

cancel(conn)

@spec cancel(db() | nil) :: :ok | {:error, reason()}

Cancel a running query: wake any busy handler sleep and interrupt VDBE execution.

This is a superset of interrupt/1 — it sets a cancel flag that the busy and progress handlers observe, and also calls sqlite3_interrupt(). After a cancel, the connection can be reused normally.

Use this when a query might be blocked either inside SQLite bytecode execution or inside the busy handler waiting for a lock.

changes(conn)

@spec changes(db()) :: {:ok, integer()} | {:error, reason()}

Get the number of changes recently.

Note: If triggers are used, the count may be larger than expected.

See: https://sqlite.org/c3ref/changes.html

close(conn)

@spec close(db() | nil) :: :ok | {:error, reason()}

Closes the database and releases any underlying resources.

columns(conn, statement)

@spec columns(db(), statement()) :: {:ok, [binary()]} | {:error, reason()}

deserialize(conn, database \\ "main", serialized)

@spec deserialize(db(), String.t(), binary()) :: :ok | {:error, reason()}

Disconnect from database and then reopen as an in-memory database based on the serialized binary.

enable_load_extension(conn, flag)

@spec enable_load_extension(db(), boolean()) :: :ok | {:error, reason()}

Allow loading native extensions.

execute(conn, sql)

@spec execute(db(), String.t()) :: :ok | {:error, reason()}

Executes an sql script. Multiple stanzas can be passed at once.

fetch_all(conn, statement)

@spec fetch_all(db(), statement()) :: {:ok, [row()]} | {:error, reason()}

fetch_all(conn, statement, chunk_size)

@spec fetch_all(db(), statement(), integer()) :: {:ok, [row()]} | {:error, reason()}

interrupt(conn)

@spec interrupt(db() | nil) :: :ok | {:error, reason()}

Interrupt a long-running query.

This calls sqlite3_interrupt() and is effective while SQLite is actively executing a statement. It does not wake the custom busy handler while the connection is sleeping and waiting on a lock. Use cancel/1 when you need to abort both statement execution and busy waits.

Warning

If you are going to interrupt a long running process, it is unsafe to call close/1 immediately after. You run the risk of undefined behavior. This is a limitation of the sqlite library itself. Please see the documentation https://www.sqlite.org/c3ref/interrupt.html for more information.

If close must be called after, it is best to put a short sleep in order to let sqlite finish doing its book keeping.

last_insert_rowid(conn)

@spec last_insert_rowid(db()) :: {:ok, integer()}

multi_step(conn, statement)

@spec multi_step(db(), statement()) ::
  :busy | {:rows, [row()]} | {:done, [row()]} | {:error, reason()}

multi_step(conn, statement, chunk_size)

@spec multi_step(db(), statement(), integer()) ::
  :busy | {:rows, [row()]} | {:done, [row()]} | {:error, reason()}

open(path, opts \\ [])

@spec open(String.t(), [open_opt()]) :: {:ok, db()} | {:error, reason()}

Opens a new sqlite database at the Path provided.

path can be ":memory" to keep the sqlite database in memory.

Options

  • :mode - use :readwrite to open the database for reading and writing , :readonly to open it in read-only mode or [:readonly | :readwrite, :nomutex] to open it with no mutex mode. :readwrite will also create the database if it doesn't already exist. Defaults to :readwrite. Note: [:readwrite, :nomutex] is not recommended.

prepare(conn, sql)

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

release(conn, statement)

@spec release(db(), statement()) :: :ok | {:error, reason()}

Once finished with the prepared statement, call this to release the underlying resources.

This should be called whenever you are done operating with the prepared statement. If the system has a high load the garbage collector may not clean up the prepared statements in a timely manner and causing higher than normal levels of memory pressure.

If you are operating on limited memory capacity systems, definitely call this.

reset(stmt)

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

Resets a prepared statement.

See: https://sqlite.org/c3ref/reset.html

serialize(conn, database \\ "main")

@spec serialize(db(), String.t()) :: {:ok, binary()} | {:error, reason()}

Serialize the contents of the database to a binary.

set_authorizer(conn, deny_list)

@spec set_authorizer(db(), [atom()]) :: :ok | {:error, reason()}

Set an authorizer that denies specific SQL operations.

Accepts a list of action atoms to deny. Any SQL statement that triggers a denied action will fail with a "not authorized" error during preparation.

Pass an empty list to clear the authorizer.

Action atoms

:attach, :detach, :pragma, :insert, :update, :delete, :create_table, :drop_table, :create_index, :drop_index, :create_trigger, :drop_trigger, :create_view, :drop_view, :alter_table, :reindex, :analyze, :function, :savepoint, :transaction, :read, :select, :recursive, :create_temp_table, :create_temp_index, :create_temp_trigger, :create_temp_view, :drop_temp_table, :drop_temp_index, :drop_temp_trigger, :drop_temp_view, :create_vtable, :drop_vtable

Examples

# Block ATTACH and DETACH (prevent cross-database reads)
:ok = Sqlite3.set_authorizer(conn, [:attach, :detach])

# Clear the authorizer
:ok = Sqlite3.set_authorizer(conn, [])

set_busy_timeout(conn, timeout_ms)

@spec set_busy_timeout(db(), integer()) :: :ok | {:error, reason()}

Set the busy timeout in milliseconds without destroying the custom busy handler.

Unlike PRAGMA busy_timeout (which internally calls sqlite3_busy_timeout() and replaces any custom handler), this function only updates the timeout value that the custom busy handler reads. This preserves the ability to cancel busy waits via cancel/1.

A timeout of 0 makes lock contention fail immediately with SQLITE_BUSY. Larger values let SQLite keep retrying until the timeout expires or the wait is cancelled.

This is the low-level API behind the :busy_timeout connection option.

set_log_hook(pid)

@spec set_log_hook(pid()) :: :ok | {:error, reason()}

Send log messages to a process.

Each time a message is logged in SQLite a message will be sent to the pid provided as the argument.

The message is of the form: {:log, rc, message}, where:

See SQLITE_CONFIG_LOG and "The Error And Warning Log" for more details.

Restrictions

  • Only one pid can listen to the log messages at a time. If this function is called multiple times, only the last pid will receive the notifications

set_progress_handler_steps(conn, steps)

@spec set_progress_handler_steps(db(), integer()) :: :ok | {:error, reason()}

Configure how often SQLite invokes the progress handler during statement execution.

The default is 1000 virtual machine steps.

Values less than 1 disable the progress handler. Larger values reduce the overhead of cancellation checks at the cost of slower response to cancel/1 and interrupt/1 while a query is running.

This is the low-level API behind the :progress_handler_steps connection option.

set_update_hook(conn, pid)

@spec set_update_hook(db(), pid()) :: :ok | {:error, reason()}

Send data change notifications to a process.

Each time an insert, update, or delete is performed on the connection provided as the first argument, a message will be sent to the pid provided as the second argument.

The message is of the form: {action, db_name, table, row_id}, where:

  • action is one of :insert, :update or :delete
  • db_name is a string representing the database name where the change took place
  • table is a string representing the table name where the change took place
  • row_id is an integer representing the unique row id assigned by SQLite

Restrictions

  • There are some conditions where the update hook will not be invoked by SQLite. See the documentation for more details
  • Only one pid can listen to the changes on a given database connection at a time. If this function is called multiple times for the same connection, only the last pid will receive the notifications
  • Updates only happen for the connection that is opened. For example, there are two connections A and B. When an update happens on connection B, the hook set for connection A will not receive the update, but the hook for connection B will receive the update.

shrink_memory(conn)

@spec shrink_memory(db()) :: :ok | {:error, reason()}

Causes the database connection to free as much memory as it can. This is useful if you are on a memory restricted system.

step(conn, statement)

@spec step(db(), statement()) :: :done | :busy | {:row, row()} | {:error, reason()}

transaction_status(conn)

@spec transaction_status(db()) :: {:ok, :idle | :transaction}