Hello!
I’m happy to announce that after far too long of a wait, we’ve finally released Rel8 1.5! Rel8 is a Haskell library for interacting with PostgreSQL databases, built on top of the fantastic Opaleye library.
The main objectives of Rel8 are:
-
Conciseness: Users using Rel8 should not need to write boiler-plate code. By using expressive types, we can provide sufficient information for the compiler to infer code whenever possible.
-
Inferrable: Despite using a lot of type level magic, Rel8 aims to have excellent and predictable type inference.
-
Familiar: writing Rel8 queries should feel like normal Haskell programming.
There are a lot of changes in this release. Before we get to that, I’m aware that this release will be a bit of a flag day event for a lot of people, and I somewhat regret that. Hopefully all of these goodies will make up for it!
Here’s the full changelog for this release:
1.5.0.0 — 2024-03-19
Removed
- Removed
nullaryFunction
. Insteadfunction
can be called with()
. (#258)
Added
-
Support PostgreSQL’s
inet
type (which maps to the HaskellNetAddr IP
type). (#227) -
Rel8.materialize
andRel8.Tabulate.materialize
, which add a materialization/optimisation fence toSELECT
statements by binding a query to aWITH
subquery. Note that explicitly materialized common table expressions are only supported in PostgreSQL 12 an higher. (#180) (#284) -
Rel8.head
,Rel8.headExpr
,Rel8.last
,Rel8.lastExpr
for accessing the first/last elements ofListTable
s and arrays. We have also added variants forNonEmptyTable
s/non-empty arrays with the1
suffix (e.g.,head1
). (#245) -
Rel8 now has extensive support for
WITH
statements and data-modifying statements (PostgreSQL: Documentation: 16: 7.8. WITH Queries (Common Table Expressions)).This work offers a lot of new power to Rel8. One new possibility is “moving” rows between tables, for example to archive rows in one table into a log table:
import Rel8 archive :: Statement () archive = do deleted <- delete Delete { from = mainTable , using = pure () , deleteWhere = \foo -> fooId foo ==. lit 123 , returning = Returning id } insert Insert { into = archiveTable , rows = deleted , onConflict = DoNothing , returning = NoReturninvg }
This
Statement
will compile to a single SQL statement - essentially:WITH deleted_rows (DELETE FROM main_table WHERE id = 123 RETURNING *) INSERT INTO archive_table SELECT * FROM deleted_rows
This feature is a significant performant improvement, as it avoids an entire roundtrip.
This change has necessitated a change to how a
SELECT
statement is ran:select
now will now produce aRel8.Statement
, which you have torun
to turn it into a HasqlStatement
. Rel8 offers a variety ofrun
functions depending on how many rows need to be returned - see the various family ofrun
functions in Rel8’s documentation for more. -
Rel8.loop
andRel8.loopDistinct
, which allow writingWITH .. RECURSIVE
queries. (#180) -
Added the
QualifiedName
type for named PostgreSQL objects (tables, views, functions, operators, sequences, etc.) that can optionally be qualified by a schema, including anIsString
instance. (#257) (#263) -
Added
queryFunction
forSELECT
ing from table-returning functions such asjsonb_to_recordset
. (#241) -
TypeName
record, which gives a richer representation of the components of a PostgreSQL type name (name, schema, modifiers, scalar/array). (#263) -
Rel8.length
andRel8.lengthExpr
for getting the lengthListTable
s and arrays. We have also added variants forNonEmptyTable
s/non-empty arrays with the1
suffix (e.g.,length1
). (#268) -
Added aggregators
listCat
andnonEmptyCat
for folding a collection of lists into a single list by concatenation. (#270) -
DBType
instance forFixed
that would map (e.g.)Micro
tonumeric(1000, 6)
andPico
tonumeric(1000, 12)
. (#280) -
aggregationFunction
, which allows custom aggregation functions to be used. (#283) -
Add support for ordered-set aggregation functions, including
mode
,percentile
,percentileContinuous
,hypotheticalRank
,hypotheticalDenseRank
,hypotheticalPercentRank
andhypotheticalCumeDist
. (#282) -
Added
index
,index1
,indexExpr
, andindex1Expr
functions for extracting individual elements fromListTable
s andNonEmptyTable
s. (#285) -
Rel8 now supports GHC 9.8. (#299)
Changed
-
Rel8’s API regarding aggregation has changed significantly, and is now a closer match to Opaleye.
The previous aggregation API had
aggregate
transform aTable
from theAggregate
context back into theExpr
context:myQuery = aggregate do a <- each tableA return $ liftF2 (,) (sum (foo a)) (countDistinct (bar a))
This API seemed convenient, but has some significant shortcomings. The new API requires an explicit
Aggregator
be passed toaggregate
:myQuery = aggregate (liftA2 (,) (sumOn foo) (countDistinctOn bar)) do each tableA
For more details, see #235
-
TypeInformation
'sdecoder
field has changed. Instead of taking aHasql.Decoder
, it now takes aRel8.Decoder
, which itself is comprised of aHasql.Decoder
and anattoparsec
Parser
. This is necessitated by the fix for #168; we generally decode things in PostgreSQL’s binary format (using aHasql.Decoder
), but for nested arrays we now get things in PostgreSQL’s text format (for which we need anattoparsec
Parser
), so must have both. MostDBType
instances that usemapTypeInformation
orParseTypeInformation
, orDerivingVia
helpers likeReadShow
,JSONBEncoded
,Enum
andComposite
are unaffected by this change. (#243) -
The
schema
field fromTableSchema
has been removed and the name field changed fromString
toQualifiedName
. (#257) -
nextval
,function
andbinaryOperator
now take aQualifiedName
instead of aString
. (#262) -
function
has been changed to accept a single argument (as opposed to variadic arguments). (#258) -
TypeInformation
'stypeName
parameter fromString
toTypeName
. (#263) -
DBEnum
'senumTypeName
method fromString
toQualifiedName
. (#263) -
DBComposite
'scompositeTypeName
method fromString
toQualifiedName
. (#263) -
Changed
Upsert
by adding apredicate
field, which allows partial indexes to be specified as conflict targets. (#264) -
The window functions
lag
,lead
,firstValue
,lastValue
andnthValue
can now operate on entire rows at once as opposed to just single columns. (#281)
Happy querying!