sqlite (engine specialist)
The SQLite-specific layer. Cross-engine conventions - schema design, migrations, indexing and transaction rules, connection handling - are the cross-engine database hub's; load that hub first where the install has it, and do not restate it. The EF Core side is the ORM-side skill's (EF Core / Dapper). This is only what changes because the engine is SQLite, and stands on its own when the hub is absent.
When it fits
- Good: embedded / desktop / mobile app storage, single-node edge, a local cache, and test databases. It is a file, not a server.
- Bad: high-write-concurrency multi-client web. SQLite allows one writer at a time for the whole database - reach for PostgreSQL there (and the house PostgreSQL skill, when your skill list has one).
Concurrency
- Enable WAL:
PRAGMA journal_mode=WAL - readers no longer block the single writer, which is the big throughput win. WAL persists on the file.
PRAGMA busy_timeout=5000 (ms) so a contended writer waits instead of failing instantly with SQLITE_BUSY.
PRAGMA synchronous=NORMAL with WAL is the usual durability/speed balance (FULL is safest, slower).
PRAGMAs and typing
PRAGMA foreign_keys=ON on every connection - FK enforcement is OFF by default.
- SQLite is dynamically typed (type affinity, not strict) - a column will accept any type. Declare
STRICT tables (3.37+) to enforce the declared types.
- No native boolean or date/time type: store dates as ISO-8601
TEXT or epoch INTEGER, booleans as 0/1. Sort/compare accordingly.
The connection-open sequence the rules above add up to:
PRAGMA foreign_keys = ON; -- every connection - enforcement is OFF by default
PRAGMA busy_timeout = 5000; -- ms - a contended writer waits instead of failing with SQLITE_BUSY
PRAGMA journal_mode = WAL; -- persists on the file; readers no longer block the single writer
PRAGMA synchronous = NORMAL; -- the usual durability/speed balance with WAL
Schema changes
ALTER TABLE is limited to RENAME, ADD COLUMN, DROP COLUMN (3.35+, but blocked on a column that is a PK / unique / indexed / in an FK / CHECK / generated expression), and toggling a column's NOT NULL (3.53+).
- Any other change - retype a column, reorder, add other constraints - needs the documented 12-step rebuild, in this order (skipping steps is how FK enforcement and views silently break):
PRAGMA foreign_keys=OFF (outside the transaction).
- Begin a transaction.
- Save the SQL of the table's indexes, triggers, and views (query
sqlite_schema).
CREATE TABLE new_X with the revised schema.
INSERT INTO new_X SELECT ... FROM X.
DROP TABLE X.
ALTER TABLE new_X RENAME TO X.
- Recreate the indexes and triggers from step 3.
- Drop and recreate any views the change affects.
PRAGMA foreign_key_check (if FKs were on).
- Commit.
PRAGMA foreign_keys=ON again.
- EF Core migrations on SQLite rebuild tables for many operations - can be slow and occasionally lossy. Review the generated SQL before applying.
Queries and indexes
- No
RIGHT/FULL OUTER JOIN before 3.39 - rewrite as LEFT JOIN.
- B-tree indexes only (no GIN/BRIN); partial and expression indexes are supported. Run
ANALYZE / PRAGMA optimize for planner statistics, and EXPLAIN QUERY PLAN to confirm an index is used.
- Full-text search: use an FTS5 virtual table, not
LIKE '%term%'.
Connections and testing
- No server, no pooling - a connection is a file handle. Keep a connection per thread; do not share one connection across threads.
- An in-memory database (
:memory:) is private to its connection unless you use a shared-cache name. For tests, hold the connection open for the database's lifetime, or the schema vanishes when it closes.
Backup
The database is a single file: copy it while idle, or use the online backup API / VACUUM INTO 'backup.db' for a consistent copy of a live database.
1---2name: sqlite3description: SQLite engine specialist - the SQLite-specific delta on top of the cross-engine database-conventions hub: when SQLite fits, the single-writer / WAL concurrency model and busy-timeout, PRAGMAs (foreign_keys, journal_mode, synchronous), type affinity vs STRICT tables and date/bool storage, limited ALTER TABLE and the table-rebuild, connection-per-thread and in-memory test DBs, B-tree-only indexing, FTS5, and backup. Load for a SQLite .db, a PRAGMA, an embedded/desktop/mobile/test store, or an EF Core SQLite provider quirk. Not the cross-engine schema/transaction rules (-> database-conventions) or a server-class concurrent-writer workload (-> postgres). Companions: database-conventions (cross-engine hub - load first), dotnet-data-access (the EF Core / ORM side), postgres (the other engine).4---56# sqlite (engine specialist)78The SQLite-specific layer. **Cross-engine conventions - schema design, migrations, indexing and transaction rules, connection handling - are the cross-engine database hub's; load that hub first where the install has it, and do not restate it.** The EF Core side is the ORM-side skill's (EF Core / Dapper). This is only what changes *because the engine is SQLite*, and stands on its own when the hub is absent.910## When it fits1112- Good: embedded / desktop / mobile app storage, single-node edge, a local cache, and test databases. It is a file, not a server.13- Bad: high-write-concurrency multi-client web. SQLite allows **one writer at a time** for the whole database - reach for PostgreSQL there (and the house PostgreSQL skill, when your skill list has one).1415## Concurrency1617- Enable WAL: `PRAGMA journal_mode=WAL` - readers no longer block the single writer, which is the big throughput win. WAL persists on the file.18- `PRAGMA busy_timeout=5000` (ms) so a contended writer waits instead of failing instantly with `SQLITE_BUSY`.19- `PRAGMA synchronous=NORMAL` with WAL is the usual durability/speed balance (`FULL` is safest, slower).2021## PRAGMAs and typing2223- `PRAGMA foreign_keys=ON` on **every connection** - FK enforcement is OFF by default.24- SQLite is dynamically typed (type *affinity*, not strict) - a column will accept any type. Declare `STRICT` tables (3.37+) to enforce the declared types.25- No native boolean or date/time type: store dates as ISO-8601 `TEXT` or epoch `INTEGER`, booleans as `0`/`1`. Sort/compare accordingly.2627The connection-open sequence the rules above add up to:2829```sql30PRAGMA foreign_keys = ON; -- every connection - enforcement is OFF by default31PRAGMA busy_timeout = 5000; -- ms - a contended writer waits instead of failing with SQLITE_BUSY32PRAGMA journal_mode = WAL; -- persists on the file; readers no longer block the single writer33PRAGMA synchronous = NORMAL; -- the usual durability/speed balance with WAL34```3536## Schema changes3738- `ALTER TABLE` is limited to `RENAME`, `ADD COLUMN`, `DROP COLUMN` (3.35+, but blocked on a column that is a PK / unique / indexed / in an FK / CHECK / generated expression), and toggling a column's `NOT NULL` (3.53+).39- Any other change - retype a column, reorder, add other constraints - needs the documented 12-step rebuild, in this order (skipping steps is how FK enforcement and views silently break):40 1. `PRAGMA foreign_keys=OFF` (outside the transaction).41 2. Begin a transaction.42 3. Save the SQL of the table's indexes, triggers, and views (query `sqlite_schema`).43 4. `CREATE TABLE new_X` with the revised schema.44 5. `INSERT INTO new_X SELECT ... FROM X`.45 6. `DROP TABLE X`.46 7. `ALTER TABLE new_X RENAME TO X`.47 8. Recreate the indexes and triggers from step 3.48 9. Drop and recreate any views the change affects.49 10. `PRAGMA foreign_key_check` (if FKs were on).50 11. Commit.51 12. `PRAGMA foreign_keys=ON` again.52- EF Core migrations on SQLite rebuild tables for many operations - can be slow and occasionally lossy. Review the generated SQL before applying.5354## Queries and indexes5556- No `RIGHT`/`FULL OUTER JOIN` before 3.39 - rewrite as `LEFT JOIN`.57- B-tree indexes only (no GIN/BRIN); partial and expression indexes are supported. Run `ANALYZE` / `PRAGMA optimize` for planner statistics, and `EXPLAIN QUERY PLAN` to confirm an index is used.58- Full-text search: use an FTS5 virtual table, not `LIKE '%term%'`.5960## Connections and testing6162- No server, no pooling - a connection is a file handle. Keep a connection per thread; do not share one connection across threads.63- An in-memory database (`:memory:`) is private to its connection unless you use a shared-cache name. For tests, hold the connection open for the database's lifetime, or the schema vanishes when it closes.6465## Backup6667The database is a single file: copy it while idle, or use the online backup API / `VACUUM INTO 'backup.db'` for a consistent copy of a live database.