v0.17.1 — SQLite Backend
Overview
Adds SQLite as an alternative database backend alongside PostgreSQL. Enables
single-binary deployments, air-gapped environments, edge nodes, and developer
laptops without requiring a PostgreSQL instance.
Architecture
Key Design Decisions
| Decision |
Rationale |
Parallel store/sqlite/ package |
Clean separation, no runtime branching in SQL |
? placeholders (not $N) |
SQLite native, no conversion overhead |
JSON text arrays ('["a","b"]') |
Replace TEXT[]/UUID[] + pq.Array |
json_each() for array membership |
Replace = ANY(array_col) |
App-side store.NewID() UUIDs |
Replace DEFAULT gen_random_uuid() |
datetime('now') |
Replace NOW() / CURRENT_TIMESTAMP |
LIKE fallback for search |
Replace tsvector / ts_rank |
| Feature-gated vector search |
pgvector → returns error hint on SQLite |
excluded.col in ON CONFLICT |
Replace Postgres $N reuse in SET clause |
| WAL mode + single writer |
Optimal SQLite concurrency for web apps |
Files Delivered
Infrastructure
| File |
Purpose |
server/config/config.go |
Added DBDriver field + DB_DRIVER env var |
server/database/dialect.go |
Dialect type, IsPostgres(), IsSQLite() |
server/database/database.go |
Dual-driver Connect(): postgres + sqlite |
server/database/migrate.go |
Dialect-aware migration runner |
server/database/migrations/sqlite/001_v017_schema.sql |
Full SQLite schema |
server/store/id.go |
store.NewID() — app-side UUID generation |
Store Layer (19 stores)
| File |
Lines |
Key Transforms |
store/sqlite/helpers.go |
Query builders with ?, JSON helpers |
|
store/sqlite/stores.go |
NewStores() constructor |
|
store/sqlite/user.go |
store.NewID(), datetime('now') |
|
store/sqlite/provider.go |
JSONB→TEXT, store.NewID() |
|
store/sqlite/catalog.go |
JSONB→TEXT scanning |
|
store/sqlite/persona.go |
json_each() for group grants |
|
store/sqlite/policy.go |
excluded.col in upsert |
|
store/sqlite/user_settings.go |
COALESCE with excluded.col |
|
store/sqlite/channel.go |
Tags as JSON text array |
|
store/sqlite/message.go |
store.NewID() |
|
store/sqlite/note.go |
LIKE search fallback, JSON tags |
|
store/sqlite/usage.go |
Dynamic ? filters |
|
store/sqlite/pricing.go |
excluded.col upserts |
|
store/sqlite/extension.go |
store.NewID() |
|
store/sqlite/attachment.go |
store.NewID() |
|
store/sqlite/knowledge_bases.go |
IN(?) replaces ANY(), vector search gated |
|
store/sqlite/groups.go |
store.NewID() |
|
store/sqlite/resource_grants.go |
json_each() for array membership |
|
store/sqlite/team.go |
store.NewID(), excluded.col |
|
store/sqlite/audit.go |
store.NewID() |
|
store/sqlite/global_config.go |
excluded.col upsert |
|
Integration
| File |
Purpose |
server/MAIN_GO_PATCH.md |
Shows exact main.go changes needed |
Pattern Conversion Reference
Placeholders
UUID Generation
Array Types
Array Membership (granted_groups)
ON CONFLICT Upserts
Timestamps
Full-Text Search
Vector Search
Configuration
Auto-detection: if DB_DRIVER is empty, inferred from DATABASE_URL format.
SQLite Pragmas (set automatically)
| Pragma |
Value |
Purpose |
journal_mode |
WAL |
Concurrent readers |
busy_timeout |
5000ms |
Retry on lock |
foreign_keys |
ON |
Enforce FK constraints |
Limitations vs PostgreSQL
| Feature |
PostgreSQL |
SQLite |
| Vector similarity search |
✅ pgvector |
❌ Feature-gated |
| Full-text search |
✅ tsvector/tsquery |
⚠ LIKE fallback |
| Concurrent writes |
✅ MVCC |
⚠ Single writer (WAL) |
| Array columns |
✅ Native |
⚠ JSON text arrays |
NUMERIC(12,6) precision |
✅ Exact |
⚠ REAL (float64) |
| GIN indexes |
✅ Native |
❌ Not available |
| Partial indexes |
✅ Full support |
✅ Supported |
| RETURNING clause |
✅ Full support |
✅ SQLite 3.35+ |
go.mod Addition
TODO (follow-up)