kura_sqlite

SQLite backend for kura. Provides kura_pool_sqlite, kura_driver_sqlite, kura_dialect_sqlite, and kura_backend_sqlite on top of the esqlite NIF driver.

Use

{deps, [
    {kura, "~> 2.4"},
    {kura_sqlite, "~> 0.2"}
]}.

Point kura at the backend in sys.config:

[{kura, [
    {repo, my_repo},
    {backend, kura_backend_sqlite},
    {database, <<"priv/my_app.db">>},   %% or <<":memory:">>
    {pool_size, 4}
]}].

On application start, Kura resolves the aggregator and auto-populates dialect, pool_module, and driver_module. Per-key overrides still win.

Status

Working: schemas, queries, migrations, INSERT … ON CONFLICT, RETURNING, transactions, JSON storage as TEXT, boolean as 0/1 (transparently round-tripped via kura_types:cast/2).

Not yet: kura_sandbox test fixtures, JSONB operators (->, ->>, @>), arrays.

kura_pool_sqlite:capabilities/0:

[returning, json, partial_indexes, transactions, savepoints, prepared_statements]

PG-only features (advisory_locks, listen_notify, arrays, jsonb) are deliberately absent; consumers that require them refuse to start on this backend via kura_capabilities:require/2.

Caveats

Upgrading from 0.5

Converting byte columns

Convert every binary, {encrypted, _} and BLOB custom column before the new version writes anything. The safest way is a kura migration that ships in the same deploy, so it runs before any other write:

up() ->
    [{execute, ~"UPDATE t SET col = CAST(col AS BLOB) WHERE typeof(col) = 'text'"}].

down() ->
    [].

On a UTF-8 database the cast keeps the bytes exactly as they are.

If the new version has already written to a UNIQUE column, the column can hold the same bytes twice, once as TEXT and once as BLOB, and the conversion fails with UNIQUE constraint failed. To list the pairs:

SELECT old.rowid AS text_row, new.rowid AS blob_row
FROM t AS old
JOIN t AS new ON new.col = CAST(old.col AS BLOB)
WHERE typeof(old.col) = 'text' AND typeof(new.col) = 'blob';

For each pair, keep one row and delete the other, first moving over anything that references the row you delete. The BLOB row is the newer one. Then run the conversion.

After the upgrade, check that no byte column still holds TEXT, for example when the application starts, and refuse to serve until it does not:

ok = kura_sqlite:check_blob_columns(my_repo, [my_user, my_api_key]).

It returns {error, [{text_in_blob_column, Table, Column}]} for each column that still holds a TEXT value, and {error, [{check_failed, Table, Column, Reason}]} for one it could not read. A column created by an earlier kura_sqlite has no CHECK, so raw SQL can still write TEXT to it; to add one, rebuild the table as SQLite's ALTER TABLE page describes.

License

MIT.