Files
orville-sqlite/README.md
T
jbrechtel dfc231604b
Build and Test / build-and-test (push) Successful in 5m33s
Update README with usage examples, module overview, and scope
2026-05-30 07:13:48 -04:00

155 lines
4.5 KiB
Markdown

# orville-sqlite
A type-safe SQLite API for Haskell, modeled after the [Flipstone Orville](https://github.com/flipstone/orville/) PostgreSQL library.
Maps Haskell data types to SQLite tables with compile-time schema guarantees.
## Status — MVP
The MVP provides the core pipeline: define columns and record marshallers, auto-migrate schemas, and execute basic CRUD queries.
## Usage
```haskell
{-# LANGUAGE DataKinds #-}
import Orville.SQLite
data Person = Person
{ personId :: Int64
, firstName :: Text
, lastName :: Text
, age :: Int64
}
deriving (Show, Eq)
-- Column definitions (type-safe, parameterized by nullability)
personIdField :: FieldDefinition 'NotNull Int64
personIdField = integerField "id"
firstNameField :: FieldDefinition 'NotNull Text
firstNameField = textField "first_name"
lastNameField :: FieldDefinition 'NotNull Text
lastNameField = textField "last_name"
ageField :: FieldDefinition 'NotNull Int64
ageField = integerField "age"
-- SqlMarshaller: maps the record to/from SQL columns
personMarshaller :: SqlMarshaller Person Person
personMarshaller =
Person
<$> marshallReadOnlyField personIdField -- auto-increment PK
<*> marshallField firstName firstNameField
<*> marshallField lastName lastNameField
<*> marshallField age ageField
-- TableDefinition ties a table name, primary key, and marshaller together
personTable :: TableDefinition Int64 Person Person
personTable =
mkTableDefinition "person" (primaryKey personId personIdField) personMarshaller
main :: IO ()
main = do
db <- openConnection "people.db"
withConnection db $ do
-- Auto-migrate: creates the table if it doesn't exist
autoMigrateSchema defaultOptions [schemaTable personTable []]
-- Insert
insertEntity personTable (Person 0 "Alice" "Smith" 30)
insertEntity personTable (Person 0 "Bob" "Jones" 25)
-- Query by primary key
mAlice <- findEntity personTable 1
-- mAlice = Just (Person 1 "Alice" "Smith" 30)
-- Find all
all <- findAll personTable
-- all = [Person 1 "Alice" "Smith" 30, Person 2 "Bob" "Jones" 25]
-- Update
updateEntity personTable (Person 1 "Alice" "Jones" 31)
-- Delete
deleteEntity personTable 2
pure ()
closeConnection db
```
The above creates this table:
```sql
CREATE TABLE IF NOT EXISTS person (
age INTEGER NOT NULL,
last_name TEXT NOT NULL,
first_name TEXT NOT NULL,
id INTEGER PRIMARY KEY
);
```
## Modules
| Module | Purpose |
|--------|---------|
| `Orville.SQLite` | Top-level re-exports |
| `Orville.SQLite.Monad` | `OrvilleM` reader monad and connection management |
| `Orville.SQLite.SqlType` | Type encoding/decoding for SQLite storage classes |
| `Orville.SQLite.FieldDefinition` | GADT column definitions with `NotNull`/`Nullable` |
| `Orville.SQLite.SqlMarshaller` | GADT record-to-table mapping (applicative syntax) |
| `Orville.SQLite.TableDefinition` | `PrimaryKey` and `mkTableDefinition` |
| `Orville.SQLite.AutoMigration` | Schema introspection via `PRAGMA table_info`, DDL generation |
| `Orville.SQLite.Execution` | `insertEntity`, `findEntity`, `findAll`, `updateEntity`, `deleteEntity` |
## Feature Coverage
### Included (MVP)
- `FieldDefinition` with `NotNull`/`Nullable` type parameters
- `SqlMarshaller` GADT with `marshallField`, `marshallReadOnlyField`, `marshallMaybe`
- `TableDefinition` with single-column primary keys
- `AutoMigration`: create tables, add nullable columns, drop columns (explicit opt-in), dry-run
- `Execution`: insert, find-by-PK, find-all, update, delete
- In-memory databases (`:memory:`) and file-based databases
### Not yet implemented
- Foreign keys and composite primary keys
- Table indexes
- Plans (N+1 query prevention via `Plan`)
- Batch operations
- Upsert (`upsertEntity`)
- `findEntitiesBy` (filtering on non-PK columns)
- `updateFields` (partial update)
- Raw SQL execution (`executeAndDecode`)
- Nested record marshalling and `prefixMarshaller`
- `AnnotatedSqlMarshaller`
- `SyntheticField`
- Connection pooling
## Development
All tooling runs inside Docker via the `./hs` wrapper. No local Haskell installation needed.
```bash
./hs stack build # build
./hs stack test # run tests (53 tests)
./hs stack ghci # REPL
./hs fourmolu -i src/ # format
```
Or use the convenience scripts:
```bash
./scripts/build # build with pedantic
./scripts/test # run tests
```
### Dependencies
- GHC 9.10.3
- [direct-sqlite](https://github.com/IreneKnapp/direct-sqlite) — low-level FFI binding to SQLite
- `text`, `bytestring`, `mtl`