Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/fourmolu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: haskell-actions/run-fourmolu@v10
- uses: actions/checkout@v7
- uses: haskell-actions/run-fourmolu@v13
with:
version: "0.15.0.0"
version: "0.19.0.1"
36 changes: 14 additions & 22 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
#
# For more information, see https://github.com/arybczak/haskell-ci
#
# version: 0.19.20260424
# version: 0.19.20260721
#
# REGENDATA ("0.19.20260424",["github","--config=cabal.haskell-ci","cabal.project"])
# REGENDATA ("0.19.20260721",["github","--config=cabal.haskell-ci","cabal.project"])
#
name: Haskell-CI
on:
Expand All @@ -35,7 +35,7 @@ jobs:
timeout-minutes:
60
container:
image: buildpack-deps:jammy
image: buildpack-deps:resolute
services:
postgres:
image: postgres:18
Expand Down Expand Up @@ -71,31 +71,21 @@ jobs:
compilerVersion: 9.6.7
setup-method: ghcup
allow-failure: false
- compiler: ghc-9.4.8
compilerKind: ghc
compilerVersion: 9.4.8
setup-method: ghcup
allow-failure: false
- compiler: ghc-9.2.8
compilerKind: ghc
compilerVersion: 9.2.8
setup-method: ghcup
allow-failure: false
fail-fast: false
steps:
- name: apt-get install
run: |
apt-get update
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common
- name: Install GHCup
run: |
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.50.1/x86_64-linux-ghcup-0.1.50.1 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
- name: Install cabal-install
run: |
"$HOME/.ghcup/bin/ghcup" install cabal 3.16.0.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
echo "CABAL=$HOME/.ghcup/bin/cabal-3.16.0.0 -vnormal+nowrap" >> "$GITHUB_ENV"
"$HOME/.ghcup/bin/ghcup" install cabal 3.16.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
echo "CABAL=$HOME/.ghcup/bin/cabal-3.16.1.0 -vnormal+nowrap" >> "$GITHUB_ENV"
- name: Install GHC (GHCup)
if: matrix.setup-method == 'ghcup'
run: |
Expand Down Expand Up @@ -148,9 +138,9 @@ jobs:
repository hackage.haskell.org
url: http://hackage.haskell.org/
EOF
if [ $((HCNUMVER >= 90800)) -ne 0 ] ; then echo "semaphore: True" >> $CABAL_CONFIG ; fi
cat >> $CABAL_CONFIG <<EOF
program-default-options
ghc-options: $GHCJOBS +RTS -M3G -RTS
jobs: 4
EOF
cat $CABAL_CONFIG
- name: versions
Expand Down Expand Up @@ -197,10 +187,12 @@ jobs:
echo "packages: ${PKGDIR_hpqtypes}" >> cabal.project
echo "package hpqtypes" >> cabal.project
echo " ghc-options: -Werror=missing-methods -Werror=missing-fields" >> cabal.project
if [ $((HCNUMVER >= 90400)) -ne 0 ] ; then echo "package hpqtypes" >> cabal.project ; fi
if [ $((HCNUMVER >= 90400)) -ne 0 ] ; then echo " ghc-options: -Werror=unused-packages" >> cabal.project ; fi
echo "package hpqtypes" >> cabal.project
echo " ghc-options: -Werror=unused-packages" >> cabal.project
echo "package hpqtypes" >> cabal.project
echo " ghc-options: -Werror=incomplete-patterns -Werror=incomplete-uni-patterns" >> cabal.project
if [ $((HCNUMVER < 90800)) -ne 0 ] ; then echo "package hpqtypes" >> cabal.project ; fi
if [ $((HCNUMVER < 90800)) -ne 0 ] ; then echo " ghc-options: -j4" >> cabal.project ; fi
cat >> cabal.project <<EOF
allow-newer: *:base
allow-newer: *:containers
Expand All @@ -221,8 +213,8 @@ jobs:
restore-keys: ${{ runner.os }}-${{ matrix.compiler }}-
- name: install dependencies
run: |
$CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all
$CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all
$CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only all
$CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only all
- name: build w/o tests
run: |
$CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/hlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ jobs:
hlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- name: 'Set up HLint'
uses: haskell-actions/hlint-setup@v2
Expand Down
1 change: 1 addition & 0 deletions .hlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Replace a $ b $ c with a . b $ c
- group: {name: dollar, enabled: true}

- ignore: {name: "Eta reduce"}
- ignore: {name: "Functor law"}
- ignore: {name: "Redundant do"}
- ignore: {name: "Use <=<"}
Expand Down
131 changes: 124 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,138 @@
# hpqtypes-1.14.1.0 (????-??-??)
* Introduce From/ToSQL instances for Word16, Word32 and Word64.
* Add support for (de)serialization of `Integer` to/from `numeric`.
# hpqtypes-2.0.0.0 (????-??-??)
* Drop support for GHC < 9.6.
* Drop support for `aeson` < 2.0.
* Remove the bundled `libpqtypes` C library. The library now executes queries
with the asynchronous API of plain `libpq` and handles the binary transport
format itself (the encoders and decoders are derived from the
`postgresql-binary` package).
* Replace per-type format strings with type Oids: the `pqFormat`, `pqFormat0`
and `pqVariables` methods of `PQFormat` are replaced by `pqOid` and
`pqArrayOid` (constants for built-in types are available in
`Database.PostgreSQL.PQTypes.Internal.Oid`).
* Change the type of `toSQL` to `t -> Maybe Encoding`, where `Encoding` is an
encoded value in the binary wire format and `Nothing` represents NULL. The
`PQDest` type family and `ParamAllocator` are gone.
* Change the type of `fromSQL` to `RowDecoder t`, a monadic parser that consumes
consecutive fields of a query result. Decoders of compound results are built
by composing decoders of their fields; the `PQBase` type family is gone.
* Remove the `PQFormat` superclass of `FromSQL`.
* Remove the `FromRow` class. Row fetching functions (`foldrDB`, `foldlDB`,
`mapDB_`, `fetchMany`, `fetchMaybe`, `fetchOne`) now take an explicit
`RowDecoder` of a row, e.g. `fetchMany ((,) <$> fromSQL <*> fromSQL)`.
* Row fetching functions no longer validate the width of the query result up
front, as the number of fields a `RowDecoder` consumes is not known
statically. A decoder that doesn't match the shape of the result throws
`RowLengthMismatch` when a row is decoded, which in particular means that
fetching from a result with no rows always succeeds.
* Row fetching functions no longer decode all rows of the result up front,
retaining them until the fold completes: each row is now decoded right
before the fold function consumes it, so e.g. `mapDB_` over a large result
runs in constant additional memory.
* Reduce the maximum arity of tuples with a `ToRow` instance from 50 to 10.
Larger sets of query parameters can be passed by combining rows with the
`:++:` type (previously known as `:*:`, renamed to avoid conflicting with
`GHC.Generics`).
* Remove the parameter of `QueryResult` along with its `Functor` and `Foldable`
instances (superseded by row fetching functions taking a `RowDecoder`).
* Remove support for encoding of composite types along with the `Composite`,
`CompositeRow`, `CompositeFromSQL`, `CompositeToSQL`, `CompositeArray1` and
`CompositeArray2` machinery. Composite types are decoded with the
`decodeComposite` combinator and no longer need to be registered (the
`csComposites` field of `ConnectionSettings` is gone). Also, decoding of
anonymous composite types is now supported.
* Add `genericDecoder`, which decodes consecutive fields of a row into a
product type with a `Generic` instance (e.g. a whole row into a tuple).
Combined with `decodeComposite`, it gives generic `FromSQL` instances for
product types corresponding to composite types.
* Add `PQFormat`, `ToSQL` and `FromSQL` instances for lists and `Vector`s
that encode and decode PostgreSQL arrays, so arrays of any type with the
relevant instances can be passed as query parameters and fetched with
plain `fromSQL`. Multi-dimensional arrays are represented by nesting
(mixed nesting of lists and `Vector`s works too). `String` keeps
corresponding to `text` and `[String]` to an array of `text`. The
`Database.PostgreSQL.PQTypes.Array` module is gone. Arrays are decoded by
the `fromSQLArray` method of `FromSQL` (with `fromSQLList` backing the list
instance on top of it), which defaults to `decodeArray fromSQL` and can be
overridden with a faster decoder; scalar types override it with the
dedicated `decodeScalarArray` combinator, which decodes the elements with a
value decoder directly.
* Remove `ArrayItemError`; errors of array element decoders report the
position of the offending element as the column of their `ConversionError`
instead.
* Remove `QueryError`, which was thrown when query execution produced no
`PGresult` (e.g. because the connection died while the query was being
sent). Such failures now throw `LibPQError`.
* The `Show` instances of `HPQTypesError` and `LibPQError` are now derived,
like those of the other error types: they print e.g.
`HPQTypesError "reason"` instead of `HPQTypesError (PostgreSQL): reason`.
* Add the `Database.PostgreSQL.PQTypes.Enum` module with `SQLEnum` and
`SQLEnumAsText` deriving-via helpers for mapping Haskell enumeration types
to values of PostgreSQL enum (or other) types. Scalar parameters work
against both text and enum columns without casts; arrays are sent as
`text[]` and need a cast against enum columns (e.g.
`... = ANY($1::my_enum_type[])`).
* Add `FromSQL` and `ToSQL` instances for `Integer` and `Scientific` (mapped to
`numeric`), `Word16`, `Word32` and `Word64`, plus a `ToSQL` instance for
`Word` (for symmetry with `Int`). `Int` and `Word` cannot be decoded reliably,
as their size is architecture-dependent; their `FromSQL` instances use
`TypeError` pointing at `Int64` and `Word64`.
* Add `FromSQL` and `ToSQL` instances for `IP` and `IPRange` (from the
`iproute` package), mapped to the `inet` and `cidr` types respectively.
Note that an `inet` value carries the length of its netmask alongside the
address, so it only decodes to a bare `IP` when the netmask covers the
whole address; otherwise decoding fails rather than dropping it.
* Add support for range types: `FromSQL` and `ToSQL` instances for `Range`
of `Int32`, `Int64`, `Scientific`, `Day`, `LocalTime` and `UTCTime`, mapped
to `int4range`, `int8range`, `numrange`, `daterange`, `tsrange` and
`tstzrange` respectively. The `Range` and `Bound` types are defined in
`Database.PostgreSQL.PQTypes.Range` and re-exported from
`Database.PostgreSQL.PQTypes`.
* Change the representation of `Interval` to mirror the wire format: three
components (microseconds, days, months) of mutually independent duration.
The type is now opaque; values are constructed with the `iyears`,
`imonths`, `idays`, `ihours`, `iminutes`, `iseconds` and `imicroseconds`
functions combined via the `Monoid` instance (the sub-day functions now
take `Int64`). The `Eq` and `Ord` instances compare the same way the
comparison operators of the server do, i.e. by the estimate with months
converted at 30 days and days at 24 hours. The `Show` instance shows the
components of the wire format instead of pretty-printing.
* Executing a `COPY` statement now throws an error saying it's not supported.
Previously it silently reported success while leaving the connection in a
copy mode, breaking its subsequent uses.
* Cancellation of a query whose execution was interrupted by an exception now
uses the new cancellation API when built against `libpq` >= 17, so the
cancellation request is sent over an encrypted connection if the main
connection is encrypted.
* Fix a bug in `connect` where interrupting it with an asynchronous exception
could lead to a use-after-free of the buffer holding the connection string.
* Fix cancellation of a query interrupted by an asynchronous exception being
unreliable: the server discards a cancellation request that reaches it before
the backend started executing the query, which it doesn't report in any way,
so an interrupted thread could end up waiting for the query to run to
completion. The request is now repeated until the query ends.
* Fix a bug in `changeAcquisitionModeTo` that led to holding on to an invalid
connection object when committing a transaction during transition from the
`AcquireAndHold` to `AcquireOnDemand` mode failed.
* Fix a bug in `withCursor` where an exception thrown from the continuation was
masked by the failure of the subsequent cursor cleanup if the enclosing
transaction was in the aborted state (in particular, this prevented restarts
of transactions run with a `RestartPredicate`).
* Fix a bug in the on demand connection acquisition mode where an exception
thrown from a query was masked by the failure of the `ROLLBACK` ending the
automatic transaction the query ran in, which happens whenever the query
leaves the connection in a state that admits no further queries.
* Fix a bug in `commit`, `rollback` and `unsafeWithoutTransaction` where a
failure of the issued `COMMIT` (e.g. due to a deferred constraint violation)
left the session in the autocommit mode instead of starting a new
transaction.
* Fix a bug in `finalizeConnectionData` where connection finalization
interrupted with an asynchronous exception while another thread was using
the connection put a value into the connection state MVar it didn't hold,
permanently deadlocking the other thread.
* Fix a bug where connection finalization interrupted with an asynchronous
exception while another thread was using the connection permanently
deadlocked the other thread.
* Fix transaction restart handling: a restarted transaction no longer runs
with asynchronous exceptions masked, asynchronous exceptions (e.g. timeouts)
no longer trigger a restart even if the restart predicate matches them, and
if a transaction fails, a subsequent failure of its cleanup no longer masks
the original exception (in particular hiding it from the restart predicate).

# hpqtypes-1.14.0.0 (2025-12-10)
* Make `begin`, `commit` and `rollback` do nothing instead of throwing an error
Expand Down
35 changes: 35 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,38 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

--------------------------------------------------------------------------

The modules

Database.PostgreSQL.PQTypes.Internal.Decoding
Database.PostgreSQL.PQTypes.Internal.Encoding
Database.PostgreSQL.PQTypes.Range

are derived from the postgresql-binary package
(https://github.com/nikita-volkov/postgresql-binary), which is distributed
under the following terms:

Copyright (c) 2014, Nikita Volkov

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,24 @@
[![Stackage LTS](https://www.stackage.org/package/hpqtypes/badge/lts)](https://www.stackage.org/lts/package/hpqtypes)
[![Stackage Nightly](https://www.stackage.org/package/hpqtypes/badge/nightly)](https://www.stackage.org/nightly/package/hpqtypes)

Efficient and easy-to-use bindings to (slightly modified) libpqtypes,
libpq extension that adds support for binary transport format and
composite types.
Efficient and easy-to-use bindings to `libpq` that use the binary
transport format for queries and their results.

Source code of libpqtypes is bundled along with the bindings.
Main features:

* Queries and their parameters are specified separately, so SQL
injection is not possible by construction.
* Queries are executed using the asynchronous API of `libpq`, so
threads blocked on database access can be interrupted with
asynchronous exceptions (in such case the query is cancelled
server-side as well).
* Conversion between Haskell types and their SQL counterparts is
handled by the `ToSQL` and `FromSQL` type classes on top of the
[postgresql-binary](https://hackage.haskell.org/package/postgresql-binary)
library, with types of query results checked against the expected
ones. Rows are decoded with composable, monadic `RowDecoder`s.
* Support for arrays (represented as plain lists or `Vector`s, with
nesting for multi-dimensional ones), anonymous and user-defined
composite types, PostgreSQL enums and NOTIFY/LISTEN.

Examples can be found in the [examples](https://github.com/scrive/hpqtypes/tree/master/examples) directory.
Loading
Loading