Skip to content

alexbiehl/sqlc-hs

Repository files navigation

sqlc-hs

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.

Installation

sqlc-hs can be used as a WASM plugin or as a process plugin. Refer to the sqlc documentation for more information.

WASM plugin (recommended)

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.

Process plugin

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-hs

Usage

Use 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.ByteString

Overrides

overrides 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.

Fields

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.

Examples

Map every bytea column to a strict ByteString:

overrides:
  - db_type: bytea
    haskell_type:
      package: bytestring
      module: Data.ByteString
      type: Data.ByteString.ByteString

Give 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.ByteString

Map 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.UserId

Type 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.UTCTime

Apply 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.Value

Override 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.Text

Naming

naming 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 '.

(Re-)Generate proto files with proto-lens

$ protoc --plugin=protoc-gen-haskell=`cabal exec which proto-lens-protoc` --haskell_out=sqlc-hs-protos/ protos/codegen.proto

About

Generate type-safe Haskell code from SQL via https://github.com/sqlc-dev/sqlc.

Resources

License

Stars

13 stars

Watchers

2 watching

Forks

Packages

 
 
 

Contributors