Changelog History
-
v0.7.0.1 Changes
October 01, 2020๐ Version 0.7
Thanks to Samuel Schlesinger, Adam Wespiser, Cullin Poreski,
Matthew Doty and Mark Wotton for tons of contributions.
๐ Version 0.7 of Squeal makes many changes.Inter-schema Foreign Key Bug
Unfortunately, there was a bug in inter-schema foreign keys in previous
๐ versions of Squeal. Essentially, it was erroneously assumed that
foreign keys always point to tables in the public schema. To remedy this
theForeignKey
type has changed kind from\>\>\> :kind 'ForeignKey 'ForeignKey :: [Symbol] -\> Symbol -\> [Symbol] -\> TableConstraint
to
\>\>\> :kind 'ForeignKey 'ForeignKey :: [Symbol] -\> Symbol -\> Symbol -\> [Symbol] -\> TableConstraint
โฌ๏ธ To upgrade your database schemas type, you will have to change, e.g.
'ForeignKey '["foo\_id1", "foo\_id2"] "foo" '["id1", "id2"]
to
'ForeignKey '["foo\_id1", "foo\_id2"] "public" "foo" '["id1", "id2"]
Locking Clauses
You can now add row level locking clauses to your
select
queriesPolymorphic Lateral Contexts
Previously, lateral contexts which are used for lateral joins
and subquery expressions had to have monomorphic lateral contexts,
which greatly reduced composability of queries involving lateral
๐ joins. Squeal 0.7 fixes this limitation, making it possible to
have polymorphic lateral context! When looking up a column heretofore,
the relevant typeclasses would search throughJoin lat from
.
This is the "correct" ordering as far as the structure from
left to right in the query, making lat consistently ordered as
one goes through nested lateral joins or nested subquery expressions.
However, it doesn't really matter how the lookup orders the columns.
And if the lookup searches through Join from lat instead then thanks
to good old Haskell lazy list appending, if a query only references
columns in from then it will work no matter the lat.
With a small proviso; if you leave lat polymorphic,
then you must qualify all columns since there could be more than
one table even if from has only one table in it.Decoders
The
DecodeRow
Monad
now has aMonadFail
instance.๐ New row decoder combinators have been added. The functions
๐appendRows
andconsRow
let you build row decoders up
from pieces.Previously, Squeal made it easy to decode enum types to Haskell
enum types (sum types with nullary constructors) so long as
the Haskell type exactly matches the enum type. However, because
of limitations in Haskell - constructors must be capitalized,
name conflicts are often disambiguated with extra letters, etc -
it's often the case that their constructors won't exactly match the
Postgres enum type's labels. The new functionenumValue
allows
to define typesafe custom enum decoders, similar to howrowValue
๐ allows to define typesafe custom composite decoders.\>\>\> :{data Dir = North | East | South | Westinstance IsPG Dir wheretype PG Dir = 'PGenum '["north", "south", "east", "west"]instance FromPG Dir where fromPG = enumValue $ label @"north" North :\* label @"south" South :\* label @"east" East :\* label @"west" West:}
Definitions
๐ New DDL statements have been added allowing to rename and
reset the schema of different schemum objects. Also, new DDL statements
have been added for adding comments to schemum objects.Procedures
๐ Squeal now supports procedure definitions and calls.
cmdTuples and cmdStatus
The
cmdTuples
andcmdStatus
functions fromLibPQ
are now
included.PQ Monad Instances
the
PQ
Monad
has been given instances forMonadCatch
,
MonadThrow
,MonadMask
,MonadBase
,MonadBaseControl
, and
MonadTransControl
.Referential Actions
A new type
ReferentialAction
has been factored out of
โก๏ธOnDeleteClause
s andOnUpdateClause
s. And Missing actions,
0๏ธโฃSetNotNull
andSetDefault
are now included.โฌ๏ธ To upgrade, change from e.g.
OnDeleteCascade
toOnDelete Cascade
.Array functions
๐ Squeal now offers typesafe indexing for fixed length arrays and matrices,
with new functionsindex1
andindex2
. And new functionsarrAny
andarrAll
have been added to enable comparisons to any or all elements
of a variable length array.Manipulations
โก๏ธ Tables being manipulated are now re-aliasable, and updates can reference
"from" clauses, actually calledUsingClause
s in Squeal, similar to deletes.Other changes
๐ New tests and bugfixes have been added. More support for encoding and decoding
of different types has been added. Time values now useiso8601
formatting
๐ท for inlining. Also, the GitHub repo has moved from using Circle CI to using
โ GitHub Actions for continuous integration testing. -
v0.6.0.2 Changes
April 23, 2020๐ Fix documentation for
defaultMode
for transactions. -
v0.6.0.1 Changes
April 16, 2020๐ Documentation fixes.
-
v0.6.0.0 Changes
April 16, 2020๐ Version 0.6
๐ Version 0.6 makes a number of large changes and additions to Squeal.
I want to thank folks who contributed issues and pull requests;
ilyakooo0, tuomohopia, league, Raveline, Sciencei, mwotton, and more.I particularly would like to thank my employer SimSpace and colleagues.
We are actively using Squeal at SimSpace which has pushed its development.My colleague Mark Wotton has also created a project
squealgen to generate
a Squeal schema directly from the database which is awesome.Module hierarchy
Squeal had been growing some rather large modules, whereas I prefer
sub-thousand line modules. Accordingly, I split up the module
hierarchy further. This means there's 60 modules which looks a little
overwhelming, but I think it makes it easier to locate functionality.
It also makes working in a single module less overwhelming.
All relevant functionality is still being exported bySqueal.PostgreSQL
.Statement Profunctors
Squeal's top level queries and manipulations left something to be desired.
BecauseQuery_
andManipulation_
were type families, they could be
a bit confusing to use. For instance,\>\>\> :{selectUser :: Query\_ DB UserId UserselectUser = select\_ (#id `as` #userId :\* #name `as` #userName) (from (table #users) & where\_ (#id .== param @1)):}\>\>\> :t selectUser selectUser :: Query'[]'[] '["public" ::: '["users" ::: 'Table ('[] :=\> UsersColumns)]] '['NotNull 'PGint4] '["userId" ::: 'NotNull 'PGint4, "userName" ::: 'NotNull 'PGtext]
So the
UserId
andUser
types are completely replaced by corresponding
Postgres types. This means that the query can be run, for instance,
with any parameter that is a generic singleton container ofInt32
.
We've lost apparent type safety. You could accidentally runselectUser
with aWidgetId
parameter instead of aUserId
and it could typecheck.That's because
Query
is a pure SQL construct, with no knowledge for
how to encode or decode Haskell values.Another annoyance of
Query_
andManipulation_
is that they must
be applied to Haskell types which exactly match their corresponding
Postgres types. So, in practice, you often end up with one-off
data type definitions just to have a type that exactly matches,
having the same field names, and the same ordering, etc. as the
returned row.Both of these issues are solved with the new
Statement
type. Let's
๐ see its definition.data Statement db x y whereManipulation:: (SOP.All (OidOfNull db) params, SOP.SListI row) =\> EncodeParams db params x-\> DecodeRow row y-\> Manipulation '[] db params row-\> Statement db x yQuery:: (SOP.All (OidOfNull db) params, SOP.SListI row) =\> EncodeParams db params x-\> DecodeRow row y-\> Query '[] '[] db params row-\> Statement db x y
๐ You can see that a
Statement
bundles either aQuery
or aManipulation
together with a way toEncodeParams
and a way toDecodeRow
. This
ties the statement to actual Haskell types. Going back to the example,\>\>\> :{selectUser :: Statement DB UserId UserselectUser = query $ select\_ (#id `as` #userId :\* #name `as` #userName) (from (table #users) & where\_ (#id .== param @1)):}
Now we really do have the type safety of only being able to
executeParams
selectUser
with aUserId
parameter. Here we've used the smart
constructorquery
which automatically uses the generic instances of
UserId
andUser
to construct a way toEncodeParams
and a way to
DecodeRow
. We can use theQuery
constructor to do custom encodings
and decodings.\>\>\> :{selectUser :: Statement DB UserId (UserId, Text) selectUser = Query enc dec sql where enc = contramap getUserId aParam dec = do uid \<- #id uname \<- #name return (uid, uname) sql = select Star (from (table #users) & where\_ (#id .== param @1)):}
EncodeParams
andDecodeRow
both have convenient APIs.EncodeParams
isContravariant
and can be composed with combinators.DecodeRow
is aMonad
and hasIsLabel
instances. SinceStatement
s bundle
both together, they formProfunctor
s, where you canlmap
over
parameters andrmap
over rows.The
Statement
Profunctor
is heavily influenced by
theStatement
Profunctor
from Nikita Volkov's excellenthasql
library,
๐ building on the use ofpostgresql-binary
for encoding and decoding.Deriving
Many Haskell types have corresponding Postgres types like
Double
corresponds tofloat8
. Squeal makes this an open relationship with the
PG
type family. Squeal 0.6 makes it easy to generatePG
of your
Haskell types, though you might have to turn on-XUndecidableInstances
,
by deriving anIsPG
instance.
In addition to having a corresponding Postgres type,
to fully embed your Haskell type you want instances ofToPG db
to
encode your type as an out-of-line parameter,FromPG
to
decode your type from a result value, andInline
to inline
values of your type directly in SQL statements.\>\>\> :{newtype CustomerId = CustomerId {getCustomerId :: Int32}deriving newtype (IsPG, ToPG db, FromPG, Inline):}\>\>\> :kind! PG CustomerIdPG CustomerId :: PGType= 'PGint4
You can even embed your Haskell records and enum types using
deriving via.\>\>\> :{data Complex = Complex {real :: Double, imaginary :: Double}deriving stock (GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving (IsPG, ToPG db, FromPG, Inline) via Composite Complex:}\>\>\> :kind! PG ComplexPG Complex :: PGType= 'PGcomposite '["real" ::: 'NotNull 'PGfloat8, "imaginary" ::: 'NotNull 'PGfloat8]\>\>\> printSQL (inline (Complex 0 1))ROW((0.0 :: float8), (1.0 :: float8))\>\>\> :{data Answer = Yes | Noderiving stock (GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving (IsPG, ToPG db, FromPG, Inline) via Enumerated Answer:}\>\>\> :kind! PG AnswerPG Answer :: PGType= 'PGenum '["Yes", "No"]\>\>\> printSQL (inline Yes) 'Yes'
You can also embed your types encoded as
Json
orJsonb
.\>\>\> :{data Foo = Bar Int | Baz Char Textderiving stock (GHC.Generic) deriving anyclass (ToJSON, FromJSON) deriving (IsPG, ToPG db, FromPG, Inline) via Jsonb Foo:}\>\>\> :kind! PG FooPG Foo :: PGType= 'PGjsonb\>\>\> printSQL (inline (Baz 'a' "aaa")) ('{"tag":"Baz","contents":["a","aaa"]}' :: jsonb)
One thing to notice about
ToParam db
is that it has an
extra parameterdb
that the other classes don't have. That's
because for some types, such as arrays and composites, you
need to know the OID of the element types in order to unambiguously
encode those types. And if the element types are user defined,
then they have to be looked up in the database. The extra parameter
lets us look through the schema for a matching type, and then
look up that type's OID.Migrations
Previously Squeal migrations could be either pure, involving only
data definitions, or impure, allowing arbitraryIO
. But, they
had to be rewindable; that is, every migration step had to have
an inverse. Squeal 0.6 generalizes to allow both invertible and
๐ one-way migrations. The core datatype for migrations, the free
๐ฆ categoryPath
has been moved to its own packagefree-categories
.Aggregation
Squeal 0.6 enables filtering and ordering for aggregate
arguments and filtering for window function arguments.arrayAgg (All #col & orderBy [AscNullsFirst #col] & filterWhere (#col .\< 100))
โฌ๏ธ To upgrade existing code, if you have an aggregate with multiple arguments,
๐ useAlls
instead ofAll
orDistincts
instead ofDistinct
๐ and if you have a window function, apply eitherWindow
orWindows
to its argument(s). Additionally, convenient functionsallNotNull
and
distinctNotNull
safely filter outNULL
.Ranges
Squeal 0.6 adds both Haskell and corresponding Postgres range types.
data Bound x= Infinite -- ^ unbounded | Closed x -- ^ inclusive | Open x -- ^ exclusivedata Range x = Empty | NonEmpty (Bound x) (Bound x)(\<=..\<=), (\<..\<), (\<=..\<), (\<..\<=) :: x -\> x -\> Range xmoreThan, atLeast, lessThan, atMost :: x -\> Range xsingleton :: x -\> Range xwhole :: Range x
Indexes and functions
๐ Squeal 0.6 adds support for creating and dropping user defined
indexes and functions to your schema, which can then be used
in statements.Lateral joins
๐ Squeal 0.6 adds support for lateral joins, which may reference previous
items.Null handling
Some null handling functions were added such as
monoNotNull
andunsafeNotNull
. Because Squeal is aggressivelyNULL
polymorphic,
sometimes inference errors can occur. You can applymonoNotNull
to fix something to be notNULL
. You can applyunsafeNotNull
when you know that something can't beNULL
, for instance if you've
filteredNULL
out of a column.Other changes
๐ Lots of other things changed.
Literal
andliteral
are now calledInline
andinline
.ColumnConstraint
is calledOptionality
.NullityType
s are calledNullTypes
. Squeal 0.6 adds support for domain types. It more carefully types
CREATE _ IF NOT EXISTS
andDROP _ IF EXISTS
definitions. The
๐จException
type was refactored to removeMaybe
s and new pattern
synonyms were defined to easily match on a few common SQL errors.
VarChar
andFixChar
types were added with smart constructors.
โ Many bugs were fixed. Also, many more tests were added and
a new benchmark suite. A lot more things were changed that I've
probably forgotten about. -
v0.5.2.0 Changes
January 06, 2020๐ Bugfix.
๐ Previously connection pools were handled by defining a
PoolPQ
Monad
with aMonadPQ
instance. Turns out this was sort of dumb. What it did was run each query by taking a connection from the pool, running the query, and then returning the connection to the pool. This is not what is usually expected in a real use case. In a real use case, like a web service, you probably want to use a single connection to serve an endpoint when it's called. -
v0.5.1
November 12, 2019