A Haskell code generator plugin for sqlc, allowing you to generate idiomatic Haskell types and functions directly from your SQL queries.
It leverages postgresql-simple, mysql-simple, and sqlite-simple, generating a thin layer on top of these well-known libraries.
sqlc-hs can be used as a WASM plugin or as a process plugin. Refer to the sqlc documentation for more information.
Every release ships a
sqlc-hs-<version>.wasm module. sqlc downloads and runs it for you — no
local installation required. Point your sqlc.yaml at the release asset and
its sha256:
version: '2'
plugins:
- name: haskell
wasm:
url: https://github.com/alexbiehl/sqlc-hs/releases/download/v0.2.0.1/sqlc-hs-v0.2.0.1.wasm
sha256: <sha256 of the .wasm file>The release notes of each release contain this snippet with the correct sha256 filled in — copy it from there.
Alternatively, install the sqlc-hs executable (e.g. via
cabal install sqlc-hs), ensure it is on your PATH when invoking sqlc, and
configure it as a process plugin:
version: '2'
plugins:
- name: haskell
process:
cmd: sqlc-hsUse the plugin for your schemas like so
sql:
- engine: postgresql
queries: query.sql
schema: schema.sql
codegen:
- out: gen
plugin: haskell
options:
cabal_package_name: your-package
cabal_package_version: 0.1.0.0
haskell_module_prefix: Database.Queries
overrides:
- db_type: bytea
haskell_type:
package: bytestring
module: Data.ByteString
type: Data.ByteString.ByteStringoverrides is a list of mappings — each entry tells sqlc-hs to map a
database type (or a specific column) to a Haskell type of your choosing. They
take precedence over the built-in type mappings for the relevant engine.
Each override accepts the following keys:
| Key | Required | Description |
|---|---|---|
db_type |
no* | The fully qualified database type to match, e.g. bytea, pg_catalog.int4, text. Matched against the column type sqlc reports. |
haskell_type |
yes | The Haskell type to use. Either a single mapping (see below) or a list of mappings — additional entries declare extra package/module dependencies to import. |
column |
no* | Match a specific column: column, table.column or schema.table.column. The table part matches the table name or its query alias; a bare column also matches aliased expression outputs (e.g. CAST(... AS TEXT) AS created_at), which carry no table. |
engine |
no | Restrict the override to a specific engine (postgresql, mysql, or sqlite). Useful when one configuration targets multiple engines. |
nullable |
no | If true, only match columns that are nullable. If false or omitted, only match columns that are NOT NULL. |
* At least one of db_type or column must be given. When both are given,
both must match.
A haskell_type mapping has three fields, all of which must be fully
qualified:
| Key | Description |
|---|---|
package |
The cabal package providing the type, e.g. bytestring. Added as a dependency in the generated cabal file. |
module |
The module to import, e.g. Data.ByteString. |
type |
The fully qualified type name, e.g. Data.ByteString.ByteString. May be a composed type like Data.Vector.Vector Data.Text.Text. |
Map every bytea column to a strict ByteString:
overrides:
- db_type: bytea
haskell_type:
package: bytestring
module: Data.ByteString
type: Data.ByteString.ByteStringGive haskell_type as a list when the type is composed from more than
one module: the first entry's type is what gets rendered, and every
entry's package/module is added as a cabal dependency and import. Here a
custom x509_certificate domain maps to Binary ByteString, which needs
both postgresql-simple and bytestring:
overrides:
- db_type: x509_certificate
haskell_type:
- type: Database.PostgreSQL.Simple.Binary Data.ByteString.ByteString
- package: postgresql-simple
module: Database.PostgreSQL.Simple
- package: bytestring
module: Data.ByteStringMap a single column (accounts.id) to a custom UserId newtype:
overrides:
- column: accounts.id
haskell_type:
package: your-package
module: Your.Types
type: Your.Types.UserIdType a computed/aliased query output that the engine can only describe as
TEXT — e.g. a normalized timestamp — as a proper UTCTime (the runtime
FromField/FromRow instances of your driver do the decoding):
overrides:
- column: last_error_at
haskell_type:
package: time
module: Data.Time
type: Data.Time.UTCTimeApply different mappings depending on the engine:
overrides:
- db_type: jsonb
engine: postgresql
haskell_type:
package: aeson
module: Data.Aeson
type: Data.Aeson.Value
- db_type: json
engine: mysql
haskell_type:
package: aeson
module: Data.Aeson
type: Data.Aeson.ValueOverride only nullable columns of a given type:
overrides:
- db_type: text
nullable: true
haskell_type:
package: text
module: Data.Text
type: GHC.Base.Maybe Data.Text.Textnaming customizes how the names of generated declarations are rendered,
using mustache-style {{variable}} templates. Every key is optional — an
omitted template falls back to the default, which reproduces sqlc-hs's
historical naming, so existing configurations keep generating byte-identical
code.
codegen:
- plugin: haskell
out: queries
options:
naming:
query: "run{{query}}"
params_constructor: "Mk{{query}}Args"
result_constructor: "{{query}}Row"
enum_constructor: "Enum_{{enum}}_{{value}}"
field: "{{column}}_of_{{table}}"| Key | Default | Context variables |
|---|---|---|
query |
query_{{query}} |
query — the query's name |
params_constructor |
Params_{{query}} |
query |
result_constructor |
Result_{{query}} |
query |
enum_constructor |
Enum_{{enum}}_{{value}} |
enum — the database type name; value — the enum value |
field |
{{prefix}}{{column}} |
column, table, table_alias, schema, prefix (see below) |
With that configuration, a ListUsers query over a users table renders as
runListUsers :: Query "ListUsers" "SELECT"
data instance Params "ListUsers" = MkListUsersArgs
{ age_of_ :: Data.Int.Int32
}
data instance Result "ListUsers" = ListUsersRow
{ id_of_users :: !Data.Int.Int32,
name_of_users :: !Data.Text.Text
}instead of the default query_ListUsers / Params_ListUsers /
Result_ListUsers with users_id / users_name fields. (Note age_of_:
the parameter has no table, so {{table}} rendered empty — see the
naming-templates golden test for the full output.)
prefix is the historical field namespacing, precomputed so the default
template needs no conditionals: the query's table alias (or, failing that,
the table name) followed by _ — and empty for table-less outputs such as
computed/aliased expressions.
Templates support plain {{variable}} interpolation only (whitespace inside
the braces is ignored); unknown variables render as the empty string, like in
mustache. Rendered names are always fixed up to be valid Haskell identifiers:
characters that cannot appear in an identifier become _, functions and
fields get a lower-case first letter (or a leading _ for digits),
constructors an upper-case one, and Haskell keywords used as field names are
suffixed with '.
$ protoc --plugin=protoc-gen-haskell=`cabal exec which proto-lens-protoc` --haskell_out=sqlc-hs-protos/ protos/codegen.proto