# SQLite
**A real SQL database inside your program, with nothing to install and no
quoting to get wrong.**
- **One binary.** SQLite is compiled in, so a built program needs no system
library and no shared object.
- **Values are always bound.** An apostrophe, a newline or a NUL byte
round-trips unchanged, and there is no string-building path for an injection.
- **Safe memory by default.** Text and blob columns are copied out before
SQLite can reuse them.
- **Change feeds.** Hooks report the rows a committed transaction touched, so
a UI can refresh when the data changes.
- **Full-text search** is built in through FTS5.
| At a glance | |
|---|---|
| Version | SQLite **3.53.4** |
| Licence | Public domain |
| Links | Statically, from `sqlite3/lib/sqlite3.a` |
| Builds with | `just sqlite` |
## Quick start
```odin
db := must(sqlite3.open("notes.db"))
defer sqlite3.close(&db)
must(sqlite3.exec(db, `CREATE TABLE IF NOT EXISTS note(id INTEGER PRIMARY KEY, body TEXT)`))
must(sqlite3.exec_args(db, `INSERT INTO note(body) VALUES (?)`, "it isn't quoted by hand"))
rows := must(sqlite3.query(db, `SELECT id, body FROM note WHERE body LIKE ?`, "%isn't%"))
defer sqlite3.finish(&rows)
for sqlite3.next(&rows) {
fmt.println(sqlite3.integer(rows, 0), sqlite3.text(rows, 1))
}
```
## Typed queries
`tools/jm-sqlgen` turns a package's SQL into Odin that the compiler checks. A
package keeps a `schema.sql` and a `queries.sql`, each opening with
`-- engine: sqlite`:
```sql
-- name: todo_state :one
-- Whether todo id exists, and whether it is done.
-- params: id: i64
SELECT done AS "done: bool" FROM todo WHERE id = @id;
```
`just sqlgen
` writes `queries_gen.odin` beside them, which holds a
`Todo_State_Row` struct and this proc:
```odin
todo_state :: proc(db: sqlite3.Db, id: i64, allocator := context.allocator) -> (
row: Todo_State_Row, found: bool, err: sqlite3.Error)
```
A `:many` query `todos` comes three ways: the cursor (`todos_open`,
`todos_next`, `todos_close`); `todos`, the cursor as a guard that closes at
the end of its block and leaves what stopped it in `rows.err`; and
`todos_all`, every row in a slice in the caller's allocator.
A swapped or missing argument is a compile error. A misspelt column fails the
generator. Editing either SQL file without regenerating fails the build,
through a compile-time hash of each. `check(db)` re-prepares every query
against a live database, so a schema that drifted from `schema.sql` fails when
the database is opened.
SQLite supplies the types. A column that reads a table column directly takes
its declared type. It is `Maybe` unless the column is NOT NULL and the
statement's bytecode shows nothing that can produce a NULL, such as an outer
join, an aggregate or a subquery. Tables must be STRICT, because only a STRICT
table holds to its declared types. An expression, or a column of a
compound SELECT, is annotated in its alias, as `done` is above.
An annotation is a claim, so `queries_gen_test.odin` tests it. Every query
runs against several data sets: empty tables, every nullable column NULL, the
extremes of each type, and each table alone. Each value read is checked
against its generated type. A parameter type that a STRICT column cannot
convert fails too. One that it can convert, such as an `i64` written to a
TEXT column, does not, so parameter annotations are only partly verified.
The tool's doc comment (`tools/jm-sqlgen/main.odin`) has the full format.
`examples/todo/store/db` is the todo app's SQL, generated this way, and
`tools/jm-sqlgen/testdata/notes` covers the result kinds and an outer join.
The same tool generates for PostgreSQL over `jm:pq`: see
[PostgreSQL](postgres.md#typed-queries).
## Watch what changed
`sqlite3.hooks` installs the connection's update, commit and rollback hooks.
A watcher buffers the rows the update hook reports and hands the buffer on at
commit. A change in a transaction that rolls back never happened, so the
rollback hook drops the buffer.
`examples/todo/store` is the pattern: its commit hook pushes each batch into a
stream pipeline, which re-runs the live queries on every batch.
## Use it from several threads
The build runs SQLite with `SQLITE_THREADSAFE=1`, because `jm:flow` exists and
a connection per worker has to be safe. Give each worker its own connection.
## Memory you can trust
> [!WARNING]
> SQLite frees the bytes behind a text or blob column on the next `step`. The
> package already copies them for you; do not reach around it with the raw API.
Under the hood: why a stale column pointer is silent
SQLite reuses its own pool rather than returning freed memory to libc. Reading
a stale pointer therefore yields the *next* row's data instead of crashing,
and AddressSanitizer cannot see it. That is why `text` and `blob` clone into
the allocator the query was given.
Under the hood: provenance, build and compile options
`sqlite3/vendor/` holds the SQLite **3.53.4** amalgamation (`sqlite3.c` and
`sqlite3.h`, source id
`bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc`). It was
taken from sqlite.org and verified against the SHA3-256 that page publishes.
SQLite is public domain, so vendoring it carries no licence obligation.
`just sqlite` compiles it once into `sqlite3/lib/sqlite3.a`, which is
gitignored and rebuilt when the amalgamation or the compile options change.
`foreign import` resolves that archive relative to the package directory. `odin check` never
opens a foreign import, so `just check` still type-checks all three targets on
one machine with no archive built.
The compile options are sqlite.org's recommended set, with four deliberate
departures, all of them in the justfile:
| Option | Recommended | Here | Why |
|---|---|---|---|
| `SQLITE_THREADSAFE` | `0` | `1` | A connection per `jm:flow` worker has to be safe. |
| `SQLITE_OMIT_AUTOINIT` | set | **not** set | With it, any call made before `sqlite3_initialize` is a segfault rather than an error. |
| `SQLITE_ENABLE_FTS5` | — | added | A full-text index. |
| `SQLITE_ENABLE_COLUMN_METADATA` | — | added, in place of `SQLITE_OMIT_DECLTYPE` | `jm-sqlgen` asks which table column a result column reads. The two options exclude each other; the archive grows by 1.7 KB. |
`SQLITE_OMIT_LOAD_EXTENSION` keeps the link from needing libdl.
## See also
- [Packages](packages.md): every package in jm
- [Streams](streams.md): the pipeline the todo app feeds its change batches into
- [UI](ui.md): the todo and files apps, which keep their data in SQLite
- [Fuzzing](fuzzing.md): the `jm:sqlite3` property suite