haskell-databaselisted
Install: claude install-skill ivelten/claude-haskell-skills
# PostgreSQL Modeling with Rel8
Default to **Rel8** for the database layer. Fall back to raw **Hasql** only when the user explicitly asks or when a query cannot be expressed through Rel8's relational combinators.
## Type-mapping decision tree
Apply this decision in order for every Haskell type that must live in PostgreSQL:
```
Is it a newtype over a primitive? → map to the underlying primitive column type
Is it a flat enum (no fields)? → PostgreSQL enum via DBEnum
Are all fields fixed at compile-time AND you never need indexed lookups on sub-fields?
→ PostgreSQL composite type (DBComposite)
Is the structure dynamic OR you need GIN-indexed sub-field search?
→ jsonb column (JSONBEncoded)
```
### Why the sub-field-indexing rule matters
A composite type column `address address_t` cannot have a regular B-tree index placed on `(address).city` without a **functional index**:
```sql
CREATE INDEX ON users ((address).city);
```
Functional indexes work but cannot participate in multi-column index strategies and require manual maintenance in migrations. If you query by a sub-field frequently, promote it to a top-level column instead. Reserve composite types for values you read/write atomically and never filter by individual fields in production queries.
---
## 1. Primitives and newtypes
Newtypes map straight to their underlying column type. The `coerce` trick keeps boilerplate minimal:
```haskell
n