5Version 0.4 strengthens Squeal's type system, adds
6support for multidimensional arrays, improves support
7for container type, improves `with` statements,
8improves runtime exceptions, accomodates SQL's three-valued logic,
9adds subquery expressions, and adds table and view type expressions.
13Squeal 0.4 renames some kinds to aid intuition:
16type RowType = [(Symbol, NullityType)] -- previously RelationType
17type FromType = [(Symbol, RowType)] -- previously RelationsType
20Null safety for array and composite types is gained by having the base
21type of an array be a `NullityType` and the base type of a composite
27 | PGvararray NullityType
28 | PGfixarray Nat NullityType
32Squeal embeds Postgres types into Haskell using data kinds and type-in-type:
35data PGType = PGbool | ..
36data NullityType = Null PGType | NotNull PGType
37type RowType = [(Symbol, NullityType)] -- previously RelationType
38type FromType = [(Symbol, RowType)] -- previously RelationsType
41In another sense, we can embed Haskell types
42into Postgres types by providing type families:
45type family PG (hask :: Type) :: PGType
46type family NullPG (hask :: Type) :: NullityType
47type family TuplePG (hask :: Type) :: [NullityType]
48type family RowPG (hask :: Type) :: RowType
51Let's look at these one by one.
53`PG` was introduced in Squeal 0.3. It was a closed type family that
54associates some Haskell types to their obvious corresponding Postgres
55types like `PG Double = 'PGfloat8`. It only worked on base types,
56no arrays or composites. Squeal 0.4 extends it to
57such container types and makes it an open type family so that
58users can make their own type instances.
60`NullPG` had a different name before but it does the obvious
61thing for Haskell with `Maybe`s:
64type family NullPG hask where
65 NullPG (Maybe hask) = 'Null (PG hask)
66 NullPG hask = 'NotNull (PG hask)
69`TuplePG` uses generics to turn tuple types (including records)
70into lists of `NullityType`s in the logical way, e.g.
71`TuplePG (Bool, Day) = '[ 'PGbool, 'PGdate]`.
73`RowPG` also uses generics to turn record types into a `RowType` in the logical way, e.g.
76>>> data Person = Person { name :: Text, age :: Int32 } deriving GHC.Generic
77>>> instance SOP.Generic Person
78>>> instance SOP.HasDatatypeInfo Person
79>>> :kind! TuplePG Person
80TuplePG Person :: [NullityType]
81= '['NotNull 'PGtext, 'NotNull 'PGint4]
82>>> :kind! RowPG Person
83RowPG Person :: [(Symbol, NullityType)]
84= '["name" ::: 'NotNull 'PGtext, "age" ::: 'NotNull 'PGint4]
87We've already seen a hint of why these types are useful in one construction
88from Squeal 0.3. Creating composite types in Postgres directly from a Haskell
89record type essentially uses `RowPG`. Another important use is in simplifying
90the type signatures for a `Query` or `Manipulation`. Very often, you will have
91a tuple type corresponding to the parameters and a record type corresponding
92to the returned columns of a `Query` or `Manipulation`. Instead of writing
93boilerplate signature you can reuse these with the help of `TuplePG` and `RowPG`
100 query :: Query '["user" ::: 'View (RowPG Person)] (TuplePG (Only Int32)) (RowPG Person)
101 query = selectStar (from (view #user) & where_ (#age .> param @1))
107In addition to being able to encode and decode basic Haskell types
108like `Int16` and `Text`, Squeal 0.4 permits you to encode and decode Haskell types to
109Postgres array types. The `Vector` type corresponds to to variable length arrays.
110And thanks to an idea from [Mike Ledger](https://github.com/mikeplus64),
111homogeneous tuples correspond to fixed length arrays. We can even
112create multi-dimensional fixed length arrays. Let's see an example.
117 { col1 :: Vector Int16
118 , col2 :: (Maybe Int16,Maybe Int16)
119 , col3 :: ((Int16,Int16),(Int16,Int16),(Int16,Int16))
120 } deriving (Eq, GHC.Generic)
123>>> instance Generic Row
124>>> instance HasDatatypeInfo Row
127Define a simple round trip query.
132 roundTrip :: Query '[] (TuplePG Row) (RowPG Row)
133 roundTrip = values_ $
134 parameter @1 (int2 & vararray) `as` #col1 :*
135 parameter @2 (int2 & fixarray @2) `as` #col2 :*
136 parameter @3 (int2 & fixarray @2 & fixarray @3) `as` #col3
139>>> :set -XOverloadedLists
140>>> let input = Row [1,2] (Just 1,Nothing) ((1,2),(3,4),(5,6))
142void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
143 result <- runQueryParams roundTrip input
144 Just output <- firstRow result
145 liftBase . print $ input == output
152Squeal aims to provide a correspondence between Haskell types and Postgres types.
153In particular, Haskell ADTs with nullary constructors can correspond to
154Postgres enum types and Haskell record types can correspond to Postgres
155composite types. However, it's not always obvious that that's how a user
156will choose to store values of those types. So Squeal 0.4 introduces newtypes
157whose purpose is to specify how a user wants to store values of a type.
160newtype Json hask = Json {getJson :: hask}
161newtype Jsonb hask = Jsonb {getJsonb :: hask}
162newtype Composite record = Composite {getComposite :: record}
163newtype Enumerated enum = Enumerated {getEnumerated :: enum}
169>>> data Schwarma = Beef | Lamb | Chicken deriving (Eq, Show, GHC.Generic)
170>>> instance SOP.Generic Schwarma
171>>> instance SOP.HasDatatypeInfo Schwarma
173>>> data Person = Person {name :: Text, age :: Int32} deriving (Eq, Show, GHC.Generic)
174>>> instance SOP.Generic Person
175>>> instance SOP.HasDatatypeInfo Person
176>>> instance Aeson.FromJSON Person
177>>> instance Aeson.ToJSON Person
180We can create the equivalent Postgres types directly from their Haskell types.
185 '[ "schwarma" ::: 'Typedef (PG (Enumerated Schwarma))
186 , "person" ::: 'Typedef (PG (Composite Person))
192 setup :: Definition '[] Schema
194 createTypeEnumFrom @Schwarma #schwarma >>>
195 createTypeCompositeFrom @Person #person
199Let's demonstrate how to associate our Haskell types `Schwarma` and `Person`
200with enumerated, composite or json types in Postgres. First create a Haskell
201`Row` type using the `Enumerated`, `Composite` and `Json` newtypes as fields.
206 { schwarma :: Enumerated Schwarma
207 , person1 :: Composite Person
208 , person2 :: Json Person
209 } deriving (Eq, GHC.Generic)
212>>> instance Generic Row
213>>> instance HasDatatypeInfo Row
218 (Composite (Person "Faisal" 24))
219 (Json (Person "Ahmad" 48))
223Once again, define a round trip query.
228 roundTrip :: Query Schema (TuplePG Row) (RowPG Row)
229 roundTrip = values_ $
230 parameter @1 (typedef #schwarma) `as` #schwarma :*
231 parameter @2 (typedef #person) `as` #person1 :*
232 parameter @3 json `as` #person2
236Finally, we can drop our type definitions.
241 teardown :: Definition Schema '[]
242 teardown = dropType #schwarma >>> dropType #person
252 result <- runQueryParams roundTrip input
253 Just output <- firstRow result
254 liftBase . print $ input == output
256 void . withConnection "host=localhost port=5432 dbname=exampledb" $
259 & pqThen (define teardown)
266Squeal 0.3 supported WITH statements but there's a couple problems with them.
267Here's the type signature for `with` in Squeal 0.3.
271 :: SOP.SListI commons
272 => NP (Aliased (Manipulation schema params)) (common ': commons)
273 -- ^ common table expressions
274 -> Manipulation (With (common ': commons) schema) params results
275 -> Manipulation schema params results
278The first problem is that `with` only works with `Manipulations`.
279It can work on `Query`s by using `queryStatement` but it still will
280return a `Manipulation`. We can fix this issue by making `with` a
281method of a type class with instances for both `Query` and `Manipulation`.
283The second problem is that all the common table expressions
284refer the base schema, whereas in SQL, each subsequent CTE can
285refer to previous CTEs as well. But this can be fixed! First define a datatype:
288data CommonTableExpression statement params schema0 schema1 where
289 CommonTableExpression
290 :: Aliased (statement schema params) (alias ::: cte)
291 -> CommonTableExpression statement params schema (alias ::: 'View cte ': schema)
294It's just a wrapper around an aliased statement, where the statement
295could be either a `Query` or `Manipulation`, but it augments the schema
296by adding a view to it. It almost looks like a morphism between schemas
297but there is no way yet to compose them. Luckily, Squeal already has
298a datatype for this, which is used for migrations, the `AlignedList`
299type which is really the "free category". So we can then define a `With` type class:
302class With statement where
304 :: AlignedList (CommonTableExpression statement params) schema0 schema1
305 -> statement schema1 params row
306 -> statement schema0 params row
309By giving `Aliasable` instances to CTEs and aligned singleton lists of CTEs (i.e. scrap-your-nils), we get a nice syntax for WITH statements in Squeal.
311Here's an example of using `with` for a `Query` and a `Manipulation`:
318 '[ "c1" ::: 'NotNull 'PGtext
319 , "c2" ::: 'NotNull 'PGtext] ]
321 '[ "c1" ::: 'NotNull 'PGtext
322 , "c2" ::: 'NotNull 'PGtext ]
324 selectStar (from (view #t1)) `as` #t2 :>>
325 selectStar (from (view #t2)) `as` #t3
326 ) (selectStar (from (view #t3)))
329WITH "t2" AS (SELECT * FROM "t1" AS "t1"), "t3" AS (SELECT * FROM "t2" AS "t2") SELECT * FROM "t3" AS "t3"
331>>> type ProductsTable = '[] :=> '["product" ::: 'NoDef :=> 'NotNull 'PGtext, "date" ::: 'Def :=> 'NotNull 'PGdate]
335 manipulation :: Manipulation
336 '[ "products" ::: 'Table ProductsTable
337 , "products_deleted" ::: 'Table ProductsTable
338 ] '[ 'NotNull 'PGdate] '[]
340 (deleteFrom #products (#date .< param @1) ReturningStar `as` #deleted_rows)
341 (insertQuery_ #products_deleted (selectStar (from (view (#deleted_rows `as` #t)))))
342in printSQL manipulation
344WITH "deleted_rows" AS (DELETE FROM "products" WHERE ("date" < ($1 :: date)) RETURNING *) INSERT INTO "products_deleted" SELECT * FROM "deleted_rows" AS "t"
347**Three Valued Logic**
349In previous versions of Squeal, conditions followed classical two valued logic
350of `true` and `false`.
354type Condition schema from grouping params =
355 Expression schema from grouping params ('NotNull 'PGbool)
358I had thought that three valued logic, which is what SQL uses, was confusing.
359However, multiple users reported being confused at being forced to do `NULL`
360handling, particularly in their left joins. Since the original motivation
361of being less confusing evaporated I decided to switch to three valued logic
362of `true`, `false` and `null_`.
366type Condition schema from grouping params =
367 Expression schema from grouping params ('Null 'PGbool)
370**Subquery Expressions**
372Squeal 0.4 adds support for subquery expressions such as `IN` and `op ANY/ALL`.
376Squeal 0.4 adds functions to define type expressions from tables and views
377and a type expression for user defined types, `typetable`, `typeview` and
380**Runtime Exceptions**
382Squeal now has an exception type which gives details on the sort of error
383encountered and handler functions.
388 { sqlExecStatus :: LibPQ.ExecStatus
389 , sqlStateCode :: Maybe ByteString
390 -- ^ https://www.postgresql.org/docs/current/static/errcodes-appendix.html
391 , sqlErrorMessage :: Maybe ByteString
393 | ResultException Text
394 | ParseException Text
396instance Exception SquealException
399 :: MonadBaseControl IO io
401 -> (SquealException -> io a) -- ^ handler
405 :: MonadBaseControl IO io
406 => (SquealException -> io a) -- ^ handler
410**Additional Changes**
412Squeal 0.4 adds `field` and `index` functions to get components of composite
413and array expressions.
415Squeal 0.4 adds a dependency on `records-sop` to offload a lot of boilerplate
416type family logic that was needed for `RowPG`.
418The above changes required major and minor changes to Squeal DSL functions.
419Please consult the documentation.
421### Version 0.3.2 - August 4, 2018
423Version 0.3.2 features extensive support for `JSON` functionality with
424more than 50 new functions.
425This work is entirely due to [Mike Ledger](https://github.com/mikeplus64)
426who has been making terrific contributions to Squeal. Thanks!
427We also got some examples in the documentation for pools submitted by
428[Raveline](https://github.com/Raveline). I'm so pleased to be
429getting pull requests and issue submissions from you all!
431### Version 0.3.1 - July 7, 2018
433Version 0.3.1 of Squeal enables the "Scrap your Nils" trick for
434heterogeneous lists of `Alias`es, `Aliased` expressions, `PGlabel`s and `By`s
435with the typeclasses `IsLabel`, `IsQualified`, `IsPGlabel`,
436and the new `Aliasable` typeclass, to eliminate all need of using `Nil` in a list.
437There were a couple minor name changes, i.e. the function `group` was renamed to `groupBy`.
438Please consult the documentation.
440### Version 0.3 - June 26, 2018
442Version 0.3 of Squeal adds views as well as composite and enumerated types to Squeal.
443To support these features, a new kind `SchemumType` was added.
452As a consequence, you will have to update your schema definitions like so:
458 '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
459 '[ "id" ::: 'Def :=> 'NotNull 'PGint4
460 , "name" ::: 'NoDef :=> 'NotNull 'PGtext
466 '[ "users" ::: 'Table (
467 '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
468 '[ "id" ::: 'Def :=> 'NotNull 'PGint4
469 , "name" ::: 'NoDef :=> 'NotNull 'PGtext
476You can now create, drop, and query views.
481 ('[] :: TableConstraints) :=>
482 '[ "a" ::: 'NoDef :=> 'Null 'PGint4
483 , "b" ::: 'NoDef :=> 'Null 'PGint4
484 , "c" ::: 'NoDef :=> 'Null 'PGint4
487 '[ "b" ::: 'Null 'PGint4
488 , "c" ::: 'Null 'PGint4
494 definition :: Definition '["abc" ::: 'Table ABC ] '["abc" ::: 'Table ABC, "bc" ::: 'View BC]
495 definition = createView #bc (select (#b :* #c :* Nil) (from (table #abc)))
496in printSQL definition
498CREATE VIEW "bc" AS SELECT "b" AS "b", "c" AS "c" FROM "abc" AS "abc";
502 definition :: Definition '["abc" ::: 'Table ABC, "bc" ::: 'View BC] '["abc" ::: 'Table ABC]
503 definition = dropView #bc
504in printSQL definition
510 query :: Query '["abc" ::: 'Table ABC, "bc" ::: 'View BC] '[] BC
511 query = selectStar (from (view #bc))
514SELECT * FROM "bc" AS "bc"
519PostgreSQL has a powerful type system. It even allows for user defined types.
520For instance, you can define enumerated types which are data types that comprise
521a static, ordered set of values. They are equivalent to Haskell algebraic data
522types whose constructors are nullary. An example of an enum type might be the days of the week,
523or a set of status values for a piece of data.
525Enumerated types are created using the `createTypeEnum` command, for example:
530 definition :: Definition '[] '["mood" ::: 'Typedef ('PGenum '["sad", "ok", "happy"])]
531 definition = createTypeEnum #mood (label @"sad" :* label @"ok" :* label @"happy" :* Nil)
533>>> printSQL definition
534CREATE TYPE "mood" AS ENUM ('sad', 'ok', 'happy');
537Enumerated types can also be generated from a Haskell algebraic data type with nullary constructors, for example:
540>>> data Schwarma = Beef | Lamb | Chicken deriving GHC.Generic
541>>> instance SOP.Generic Schwarma
542>>> instance SOP.HasDatatypeInfo Schwarma
544>>> :kind! EnumFrom Schwarma
545EnumFrom Schwarma :: PGType
546= 'PGenum '["Beef", "Lamb", "Chicken"]
550 definition :: Definition '[] '["schwarma" ::: 'Typedef (EnumFrom Schwarma)]
551 definition = createTypeEnumFrom @Schwarma #schwarma
553>>> printSQL definition
554CREATE TYPE "schwarma" AS ENUM ('Beef', 'Lamb', 'Chicken');
557You can express values of an enum type using `label`, which is an overloaded method
558of the `IsPGlabel` typeclass.
563 expression :: Expression sch rels grp params ('NotNull (EnumFrom Schwarma))
564 expression = label @"Chicken"
565in printSQL expression
572In addition to enum types, you can define composite types.
573A composite type represents the structure of a row or record;
574it is essentially just a list of field names and their data types.
577`createTypeComposite` creates a composite type. The composite type is
578specified by a list of attribute names and data types.
583 definition :: Definition '[] '["complex" ::: 'Typedef ('PGcomposite '["real" ::: 'PGfloat8, "imaginary" ::: 'PGfloat8])]
584 definition = createTypeComposite #complex (float8 `As` #real :* float8 `As` #imaginary :* Nil)
586>>> printSQL definition
587CREATE TYPE "complex" AS ("real" float8, "imaginary" float8);
590Composite types are almost equivalent to Haskell record types.
591However, because of the potential presence of `NULL`
592all the record fields must be `Maybe`s of basic types.
593Composite types can be generated from a Haskell record type, for example:
596>>> data Complex = Complex {real :: Maybe Double, imaginary :: Maybe Double} deriving GHC.Generic
597>>> instance SOP.Generic Complex
598>>> instance SOP.HasDatatypeInfo Complex
600>>> :kind! CompositeFrom Complex
601CompositeFrom Complex :: PGType
602= 'PGcomposite '['("real", 'PGfloat8), '("imaginary", 'PGfloat8)]
606 definition :: Definition '[] '["complex" ::: 'Typedef (CompositeFrom Complex)]
607 definition = createTypeCompositeFrom @Complex #complex
608in printSQL definition
610CREATE TYPE "complex" AS ("real" float8, "imaginary" float8);
613A row constructor is an expression that builds a row value
614(also called a composite value) using values for its member fields.
619 i :: Expression '[] '[] 'Ungrouped '[] ('NotNull (CompositeFrom Complex))
620 i = row (0 `As` #real :* 1 `As` #imaginary :* Nil)
626You can also use `(&)` to apply a field label to a composite value.
631 expr :: Expression '[] '[] 'Ungrouped '[] ('Null 'PGfloat8)
632 expr = i & #imaginary
638Both composite and enum types can be automatically encoded from and decoded to their equivalent Haskell types.
639And they can be dropped.
644 definition :: Definition '["mood" ::: 'Typedef ('PGenum '["sad", "ok", "happy"])] '[]
645 definition = dropType #mood
647>>> printSQL definition
651**Additional Changes**
653Squeal 0.3 also introduces a typeclass `HasAll` similar to `Has` but for a list of aliases.
654This makes it possible to clean up some unfortunately messy Squeal 0.2 definitions.
658>>> unique (Column #a :* Column #b :* Nil)
661>>> unique (#a :* #b :* Nil)
664Squeal 0.3 also adds `IsLabel` instances for `Aliased` expressions and tables as well as
665heterogeneous lists, allowing for some more economy of code.
668-- Squeal 0.2 (or 0.3)
669>>> select (#a `As` #a :* Nil) (from (table (#t `As` #t)))
672>>> select #a (from (table #t))
675Squeal 0.3 also fixes a bug that prevented joined queries on self-referencing tables.
677The above changes required major and minor changes to Squeal DSL functions.
678Please consult the documentation.
680### Version 0.2.1 - April 7, 2018
682This minor update fixes an issue where alias identifiers could conflict with
683reserved words in PostgreSQL. To fix the issue, alias identifiers are now
684quoted. Thanks to Petter Rasmussen for the fix.
686### Version 0.2 - March 26, 2018
689- **Constraints** - Type level table constraints like primary and foreign keys and column constraints like having `DEFAULT`.
690- **Migrations** - Support for linear, invertible migrations tracked in an auxiliary table
691- **Arrays** - Support for fixed- and variable-length arrays
692- **Aliases** - Generalized `Has` constraint
693- **Pools** - Support for pooled connections
694- **Transactions** - Support for transaction control language
695- **Queries, Manipulations, Definitions** - Small and large changes to Squeal's DSL
697Well, a lot of things changed!
701An important request was to bring constraints to the type level.
702This means that more of the schema is statically known. In `0.1` column constraints - which boil down
703to having `DEFAULT` or not - were at the type level, but they were confusingly named.
7060.1: 'Optional ('NotNull 'PGInt4)
7070.2: 'Def :=> 'NotNull 'PGInt4
7080.1: 'Required ('NotNull 'PGInt4)
7090.2: 'NoDef :=> 'NotNull 'PGInt4
712The `:=>` type operator is intended to helpfully connote a constraint relation.
713It's also used for table constraints which are new in `0.2`.
717 '[ "pk_emails" ::: 'PrimaryKey '["id"]
718 , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
720 '[ "id" ::: 'Def :=> 'NotNull 'PGint4
721 , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
722 , "email" ::: 'NoDef :=> 'Null 'PGtext
726Another change in the constraint system was the removal of column constraints from
727query and manipulation results, as result columns don't support a notion of `DEFAULT`.
728This necessitates a distinction between `TableType`s which have both column and table
729constraints and `RelationType`s which have neither.
733Migrations are a hot topic and many people have requested this feature. Squeal `0.2`
734adds support for linear, invertible migrations. That is, a migration is a named,
735invertible, schema-tracking computation:
738data Migration io schema0 schema1 = Migration
739 { name :: Text -- ^ The `name` of a `Migration`.
740 -- Each `name` in a `Migration` should be unique.
741 , up :: PQ schema0 schema1 io () -- ^ The `up` instruction of a `Migration`.
742 , down :: PQ schema1 schema0 io () -- ^ The `down` instruction of a `Migration`.
746And, `Migration`s can be put together in an "aligned" list:
749data AlignedList p x0 x1 where
750 Done :: AlignedList p x x
751 (:>>) :: p x0 x1 -> AlignedList p x1 x2 -> AlignedList p x0 x2
754These aligned lists are free categories and might look familiar from
755the [reflections without remorse](http://okmij.org/ftp/Haskell/zseq.pdf) technique,
756which uses their cousins, aligned sequences.
758In the context of migration, they allow one to chain new migrations as a
759schema evolves over time. `Migration`s' execution is tracked in an auxiliary
760migrations table. Migration lists can then be run or rewinded.
764 :: MonadBaseControl IO io
765 => AlignedList (Migration io) schema0 schema1 -- ^ migrations to run
767 ("schema_migrations" ::: MigrationsTable ': schema0)
768 ("schema_migrations" ::: MigrationsTable ': schema1)
772 :: MonadBaseControl IO io
773 => AlignedList (Migration io) schema0 schema1 -- ^ migrations to rewind
775 ("schema_migrations" ::: MigrationsTable ': schema1)
776 ("schema_migrations" ::: MigrationsTable ': schema0)
782In Squeal `0.1`, we had different typeclasses `HasColumn` and `HasTable` to indicate
783that a table has a column or that a schema has a table. In Squeal `0.2` this has been
784unified to a single typeclass,
787class KnownSymbol alias =>
788 Has (alias :: Symbol) (fields :: [(Symbol,kind)]) (field :: kind)
789 | alias fields -> field where
794Support for array types has been added to Squeal `0.2` through
795the `'PGfixarray` and `'PGvararray` `PGType`s. Array values can be
796constructed using the `array` function and can be encoded from and decoded to
801Squeal `0.2` provides a monad transformer `PoolPQ` that's an instance of `MonadPQ`.
802The `resource-pool` library is leveraged to provide striped pools of `Connection`s.
803`PoolPQ` should be a drop in replacement for running `Manipulation`s and `Query`s with
808Squeal `0.2` supports a simple transaction control language. A computation in
809`MonadPQ` can be called `transactionally` with different levels of isolation.
810Additionally, a schema changing computation, a data definition, can be run in a
811transaction. Running a computation in a transaction means that all SQL statements
812will be rolled back if an exception is encountered.
814**Queries, Manipulations and Definitions**
816The above changes required major and minor changes to Squeal DSL functions.
817Please consult the documentation.
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list