polytropos-hasql
Copyright(c) 2026 The Polytropos Authors
LicenseEUPL-1.2 OR Apache-2.0
Maintainernadia.yvette.chambers@ik.me
Stabilityexperimental
Portabilitynon-portable (GHC extensions)
Safe HaskellNone
LanguageGHC2021

Polytropos.Hasql

Description

Integrates polytropos open records with the high-performance PostgreSQL driver hasql.

Architectural Overview & Rationale

Bypassing Tuple Limits & Intermediate Allocations

Conventional database libraries map SQL rows to Haskell data types via intermediate tuples (e.g. (a, b, c)). This imposes GHC's 62-column tuple limit and generates large numbers of short-lived heap objects.

recordRow builds a Row (Record r) that consumes one column per row field, in row order, and packs the decoded values straight into the contiguous SmallArray payload of the record. The column decoders are combined by the AllRowDecoder constraint walk in a single pass, appending one slot at a time. Rows of arbitrary arity (50, 100, 200+ columns) decode with zero tuple overhead.

Each column picks its value decoder from the Column class at that field's own type, allowing heterogeneous column rows.

When to Use This Module

  • When executing high-throughput PostgreSQL queries with hasql where rows have many columns or where open records are used as query results.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}

import qualified Hasql.Decoders as HD
import Polytropos
import Polytropos.Hasql

type UserRow = Record '["id" ':= Int64, "age" ':= Int32]

userDecoder :: HD.Row UserRow
userDecoder = recordRow @UserRow
Synopsis

Row Decoder Construction

type HasqlRow = Row Source #

hasql's row decoder monad; aliased to avoid clashing with the Row kind synonym from Polytropos.Core.

recordRow :: forall (r :: Row). AllRowDecoder r => HasqlRow (Record r) Source #

Build a hasql row decoder that consumes one column per field of the row, in row order, and materialises the record directly: the decoded slots go straight into a contiguous SmallArray payload via productRep. No intermediate tuples — the walk composes the column decoders applicatively, so arity is unbounded.

Column-Decoder Walk

class AllRowDecoder (r :: Row) where Source #

Heterogeneous column walk: every field of the row r has a Column instance at its own type. genRow composes one Row column decoder per field, in row order, collecting the decoded values as erased payload slots — a single pass, one slot appended per column, no tuples.

Methods

genRow :: Proxy r -> HasqlRow [Identity (Any :: Type)] Source #

Instances

Instances details
AllRowDecoder ('[] :: [Pair Symbol Type]) Source # 
Instance details

Defined in Polytropos.Hasql

Methods

genRow :: Proxy ('[] :: [Pair Symbol Type]) -> HasqlRow [Identity (Any :: Type)] Source #

(Column a, AllRowDecoder r) => AllRowDecoder ((n ':= a) ': r) Source # 
Instance details

Defined in Polytropos.Hasql

Methods

genRow :: Proxy ((n ':= a) ': r) -> HasqlRow [Identity (Any :: Type)] Source #

Per-Column Value Decoders

class Column a where Source #

The hasql value decoder for a column of type a. Instances pick the Postgres column type; the row walk only needs the column combinator.

Instances

Instances details
Column Int16 Source # 
Instance details

Defined in Polytropos.Hasql

Methods

columnValue :: Value Int16 Source #

Column Int32 Source # 
Instance details

Defined in Polytropos.Hasql

Methods

columnValue :: Value Int32 Source #

Column Int64 Source # 
Instance details

Defined in Polytropos.Hasql

Methods

columnValue :: Value Int64 Source #