← ClaudeAtlas

haskell-databaselisted

Model PostgreSQL databases from Haskell types using Rel8. Covers the type-mapping decision tree (composite types vs jsonb vs enums vs primitives), indexed JSONB queries, composite-type indexing caveats, Rel8 table definitions with HKD, and pure-SQL migrations via hasql-migrations. Use when designing the database layer, choosing PostgreSQL column types for Haskell ADTs, writing Rel8 queries, or setting up migrations.
ivelten/claude-haskell-skills · ★ 0 · API & Backend · score 60
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