@@ -1,7 +1,19 @@
1{-# language DuplicateRecordFields #-}
1{-# language DuplicateRecordFields #-}
4+ ( -- * Getting Started
4− ( -- * Database types
16+ -- * Database types
5 -- ** @DBType@
17 -- ** @DBType@
6 DBType(..)
18 DBType(..)
@@ -13,10 +25,6 @@ module Rel8
13 -- **** @ReadShow@
25 -- **** @ReadShow@
14 , ReadShow(..)
26 , ReadShow(..)
17− , Composite(..), DBComposite(..), compose, decompose
18− , Enum(..), DBEnum(..), Enumable
20 -- *** @TypeInformation@
28 -- *** @TypeInformation@
21 , TypeInformation(..)
29 , TypeInformation(..)
22 , mapTypeInformation
30 , mapTypeInformation
@@ -28,42 +36,32 @@ module Rel8
29 , DBIntegral
37 , DBIntegral
30 , DBFractional
38 , DBFractional
33 -- * Tables and higher-kinded tables
40 -- * Tables and higher-kinded tables
34− , Rel8able, KRel8able
41+ , Rel8able
36− , HADT
43+ , Default, Label
40 , HNonEmpty
46 , HNonEmpty
45− , Table(..)
49+ , Table
48 , AltTable((<|>:))
50 , AltTable((<|>:))
49− , AlternativeTable( emptyTable )
50− , EqTable(..), (==:), (/=:)
51+ , EqTable, (==:), (/=:)
51− , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
52+ , OrdTable, ascTable, descTable
57 -- ** @MaybeTable@
57 -- ** @MaybeTable@
58 , MaybeTable
58 , MaybeTable
59 , maybeTable, ($?), nothingTable, justTable
59 , maybeTable, ($?), nothingTable, justTable
60 , isNothingTable, isJustTable
60 , isNothingTable, isJustTable
62 , optional
61 , optional
63 , catMaybeTable
62 , catMaybeTable
64 , traverseMaybeTable
64 , traverseMaybeTable
65− , aggregateMaybeTable
68 -- ** @EitherTable@
66 -- ** @EitherTable@
69 , EitherTable
67 , EitherTable
@@ -71,9 +69,8 @@ module Rel8
71 , isLeftTable, isRightTable
69 , isLeftTable, isRightTable
72 , keepLeftTable
70 , keepLeftTable
73 , keepRightTable
71 , keepRightTable
74 , bitraverseEitherTable
73 , bitraverseEitherTable
75− , aggregateEitherTable
78 -- ** @TheseTable@
75 -- ** @TheseTable@
79 , TheseTable
76 , TheseTable
@@ -81,73 +78,22 @@ module Rel8
81 , isThisTable, isThatTable, isThoseTable
78 , isThisTable, isThatTable, isThoseTable
82 , hasHereTable, hasThereTable
79 , hasHereTable, hasThereTable
83 , justHereTable, justThereTable
80 , justHereTable, justThereTable
86 , keepHereTable, loseHereTable
82 , keepHereTable, loseHereTable
87 , keepThereTable, loseThereTable
83 , keepThereTable, loseThereTable
88 , keepThisTable, loseThisTable
84 , keepThisTable, loseThisTable
89 , keepThatTable, loseThatTable
85 , keepThatTable, loseThatTable
90 , keepThoseTable, loseThoseTable
86 , keepThoseTable, loseThoseTable
91 , bitraverseTheseTable
88 , bitraverseTheseTable
92− , aggregateTheseTable
95 -- ** @ListTable@
90 -- ** @ListTable@
96 , ListTable
91 , ListTable
104 -- ** @NonEmptyTable@
94 -- ** @NonEmptyTable@
105 , NonEmptyTable
95 , NonEmptyTable
106− , nonEmptyTable, ($+)
115− , nullableTable, nullTable, nullifyTable
116− , isNullTable, isNonNullTable
119− , toNullTable, toMaybeTable
121− -- ** Algebraic data types / sum types
124− -- *** Naming of ADTs
129− -- *** Deconstruction of ADTs
131− , DeconstructADT, deconstructADT
133− -- *** Construction of ADTs
135− , BuildADT, buildADT
136− , ConstructADT, constructADT
138− -- *** Other ADT operations
139− , AggregateADT, aggregateADT
141− -- *** Miscellaneous notes
146− , BuildHKD, buildHKD
147− , ConstructHKD, constructHKD
148− , DeconstructHKD, deconstructHKD
150− , AggregateHKD, aggregateHKD
152 -- ** Table schemas
98 -- ** Table schemas
153 , TableSchema(..)
99 , TableSchema(..)
@@ -160,20 +106,16 @@ module Rel8
161 , litExpr
107 , litExpr
162 , unsafeCastExpr
108 , unsafeCastExpr
165 -- ** @null@
110 -- ** @null@
170 , nullify
112 , nullify
171 , nullable
113 , nullable
173 , isNonNull
115 , isNonNull
174− , mapNull
116+ , mapNullable
175− , liftOpNull
117+ , liftOpNullable
176− , catNull
118+ , catNullable
177 , coalesce
119 , coalesce
179 -- ** Boolean operations
121 -- ** Boolean operations
@@ -184,7 +126,6 @@ module Rel8
184 , (==.), (/=.), (==?), (/=?)
126 , (==.), (/=.), (==?), (/=?)
186 , boolExpr, caseExpr
128 , boolExpr, caseExpr
189 -- ** Ordering
130 -- ** Ordering
@@ -202,13 +143,6 @@ module Rel8
203 , showQuery
144 , showQuery
207− , Projectable( project )
208− , Biprojectable( biproject )
212 -- ** Selecting rows
146 -- ** Selecting rows
213 , Selects
147 , Selects
@@ -217,8 +151,8 @@ module Rel8
217 -- ** Filtering
151 -- ** Filtering
220− , present
154+ , whereExists
221− , absent
155+ , whereNotExists
222 , distinct
156 , distinct
223 , distinctOn
157 , distinctOn
224 , distinctOnBy
158 , distinctOnBy
@@ -248,16 +182,14 @@ module Rel8
249 -- ** Aggregation
183 -- ** Aggregation
250 , Aggregate
184 , Aggregate
252 , aggregate
185 , aggregate
253 , countRows
186 , countRows
255 , listAgg, listAggExpr
187 , listAgg, listAggExpr
257 , nonEmptyAgg, nonEmptyAggExpr
188 , nonEmptyAgg, nonEmptyAggExpr
258 , DBMax, max
190 , DBMax, max
259 , DBMin, min
191 , DBMin, min
260− , DBSum, sum, sumWhere, avg
192+ , DBSum, sum, sumWhere
261 , DBString, stringAgg
193 , DBString, stringAgg
263 , countStar
195 , countStar
@@ -266,6 +198,11 @@ module Rel8
203+ , distributeAggregate
269 -- ** Ordering
206 -- ** Ordering
270 , orderBy
207 , orderBy
@@ -274,71 +211,43 @@ module Rel8
274 , nullsFirst
211 , nullsFirst
275 , nullsLast
212 , nullsLast
277− -- ** Window functions
304 , Serializable
215 , Serializable
308 -- * Running statements
217 -- * Running statements
311 -- ** @SELECT@
218 -- ** @SELECT@
314 -- ** @INSERT@
221 -- ** @INSERT@
315 , Insert(..)
222 , Insert(..)
316 , OnConflict(..)
223 , OnConflict(..)
319− , unsafeDefault
225+ , toInsert
320− , showInsert
226+ , toInsertDefaults
322 -- ** @DELETE@
228 -- ** @DELETE@
323 , Delete(..)
229 , Delete(..)
327 -- ** @UPDATE@
232 -- ** @UPDATE@
330− , showUpdate
234+ , Update(..)
332 -- ** @.. RETURNING@
236 -- ** @.. RETURNING@
333 , Returning(..)
237 , Returning(..)
335 -- ** @CREATE VIEW@
239 -- ** @CREATE VIEW@
336 , createView
240 , createView
337− , createOrReplaceView
339− -- ** Sequences
242+ -- * TODO
340− , nextval
243+ -- TODO
341− , evaluate
244+ -- These need organizing, but are reachable from Rel8's documentation so we
245+ -- do need to export and document them.
@@ -346,94 +255,65 @@ import Prelude ()
348import Rel8.Aggregate
257import Rel8.Aggregate
350−import Rel8.Column.ADT
351−import Rel8.Column.Either
352−import Rel8.Column.Lift
353−import Rel8.Column.List
354−import Rel8.Column.Maybe
355−import Rel8.Column.NonEmpty
356−import Rel8.Column.Null
357−import Rel8.Column.These
358import Rel8.Expr
258import Rel8.Expr
359import Rel8.Expr.Aggregate
259import Rel8.Expr.Aggregate
360import Rel8.Expr.Bool
260import Rel8.Expr.Bool
361−import Rel8.Expr.Default
362import Rel8.Expr.Eq
261import Rel8.Expr.Eq
363import Rel8.Expr.Function
262import Rel8.Expr.Function
364import Rel8.Expr.Null
263import Rel8.Expr.Null
365−import Rel8.Expr.Opaleye (unsafeCastExpr, unsafeLiteral)
366import Rel8.Expr.Ord
264import Rel8.Expr.Ord
367import Rel8.Expr.Order
265import Rel8.Expr.Order
368import Rel8.Expr.Serialize
266import Rel8.Expr.Serialize
369−import Rel8.Expr.Sequence
370−import Rel8.Expr.Text ( like, ilike )
371−import Rel8.Expr.Window hiding ( cumulative )
372−import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
373import Rel8.Order
267import Rel8.Order
374import Rel8.Query
268import Rel8.Query
375import Rel8.Query.Aggregate
269import Rel8.Query.Aggregate
376import Rel8.Query.Distinct
270import Rel8.Query.Distinct
377import Rel8.Query.Each
271import Rel8.Query.Each
378import Rel8.Query.Either
272import Rel8.Query.Either
379−import Rel8.Query.Evaluate
380import Rel8.Query.Exists
273import Rel8.Query.Exists
381import Rel8.Query.Filter
274import Rel8.Query.Filter
382−import Rel8.Query.Indexed
383import Rel8.Query.Limit
275import Rel8.Query.Limit
384import Rel8.Query.List
276import Rel8.Query.List
385import Rel8.Query.Maybe
277import Rel8.Query.Maybe
386import Rel8.Query.Null
278import Rel8.Query.Null
387import Rel8.Query.Order
279import Rel8.Query.Order
388−import Rel8.Query.Rebind
389import Rel8.Query.SQL (showQuery)
280import Rel8.Query.SQL (showQuery)
390import Rel8.Query.Set
281import Rel8.Query.Set
391import Rel8.Query.These
282import Rel8.Query.These
392import Rel8.Query.Values
283import Rel8.Query.Values
393−import Rel8.Query.Window
284+import Rel8.Schema.Column
285+import Rel8.Schema.Context.Label
394−import Rel8.Schema.Field
286+import Rel8.Schema.Generic
395import Rel8.Schema.HTable
287import Rel8.Schema.HTable
396import Rel8.Schema.Name
288import Rel8.Schema.Name
397−import Rel8.Schema.Null hiding ( nullable )
289+import Rel8.Schema.Nullability
398−import Rel8.Schema.Result ( Result )
399import Rel8.Schema.Table
290import Rel8.Schema.Table
400import Rel8.Statement.Delete
291import Rel8.Statement.Delete
401import Rel8.Statement.Insert
292import Rel8.Statement.Insert
402−import Rel8.Statement.OnConflict
403import Rel8.Statement.Returning
293import Rel8.Statement.Returning
404import Rel8.Statement.Select
294import Rel8.Statement.Select
405−import Rel8.Statement.SQL
406import Rel8.Statement.Update
295import Rel8.Statement.Update
407import Rel8.Statement.View
296import Rel8.Statement.View
408import Rel8.Table
297import Rel8.Table
409−import Rel8.Table.ADT
410import Rel8.Table.Aggregate
298import Rel8.Table.Aggregate
411import Rel8.Table.Alternative
299import Rel8.Table.Alternative
412import Rel8.Table.Bool
300import Rel8.Table.Bool
413import Rel8.Table.Either
301import Rel8.Table.Either
414import Rel8.Table.Eq
302import Rel8.Table.Eq
415−import Rel8.Table.HKD
303+import Rel8.Table.Insert
416import Rel8.Table.List
304import Rel8.Table.List
417import Rel8.Table.Maybe
305import Rel8.Table.Maybe
418import Rel8.Table.Name
306import Rel8.Table.Name
419import Rel8.Table.NonEmpty
307import Rel8.Table.NonEmpty
420−import Rel8.Table.Null
421−import Rel8.Table.Opaleye ( castTable )
422import Rel8.Table.Ord
308import Rel8.Table.Ord
423import Rel8.Table.Order
309import Rel8.Table.Order
424−import Rel8.Table.Projection
425−import Rel8.Table.Rel8able ()
426import Rel8.Table.Serialize
310import Rel8.Table.Serialize
427import Rel8.Table.These
311import Rel8.Table.These
428−import Rel8.Table.Transpose
429−import Rel8.Table.Window
430import Rel8.Type
312import Rel8.Type
431−import Rel8.Type.Composite
432import Rel8.Type.Eq
313import Rel8.Type.Eq
433−import Rel8.Type.Enum
434import Rel8.Type.Information
314import Rel8.Type.Information
435−import Rel8.Type.JSONBEncoded
436import Rel8.Type.JSONEncoded
315import Rel8.Type.JSONEncoded
316+import Rel8.Type.JSONBEncoded
437import Rel8.Type.Monoid
317import Rel8.Type.Monoid
438import Rel8.Type.Num
318import Rel8.Type.Num
439import Rel8.Type.Ord
319import Rel8.Type.Ord
@@ -441,216 +321,362 @@ import Rel8.Type.ReadShow
441import Rel8.Type.Semigroup
321import Rel8.Type.Semigroup
442import Rel8.Type.String
322import Rel8.Type.String
443import Rel8.Type.Sum
323import Rel8.Type.Sum
444−import Rel8.Window
324+import Rel8.Expr.Opaleye (unsafeCastExpr)
447−-- $running
327+-- $setup
448−-- To run queries and otherwise interact with a PostgreSQL database, Rel8
328+--
449−-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
329+-- In this section, we'll take a look at using Rel8 to work with a small
450−-- 'insert', 'update' and 'delete' will generally need the
330+-- database for Haskell packages. We'll take a look at idiomatic usage of Rel8,
451−-- `DuplicateRecordFields` language extension enabled.
331+-- mapping tables to Haskell, and then look at writing some simple queries.
453−-- $adts
333+-- The documentation in this library uses the following language extensions:
454−-- Algebraic data types can be modelled between Haskell and SQL.
334+--
335+-- >>> :set -XBlockArguments
336+-- >>> :set -XDeriveAnyClass
337+-- >>> :set -XDeriveGeneric
338+-- >>> :set -XDerivingStrategies
339+-- >>> :set -XDerivingVia
340+-- >>> :set -XDuplicateRecordFields
341+-- >>> :set -XGeneralizedNewtypeDeriving
342+-- >>> :set -XOverloadedStrings
343+-- >>> :set -XStandaloneDeriving
344+-- >>> :set -XTypeApplications
345+-- >>> :set -XTypeFamilies
347+-- Before we start writing any Haskell, let's take a look at the schema we'll
350+-- > author: project:
352+-- > Column │ Type │ Nullable Column │ Type │ Nullable
353+-- > ═══════════╪═════════╪══════════ ═══════════╪═════════╪══════════
354+-- > author_id │ integer │ not null author_id │ integer │ not null
355+-- > name │ text │ not null name │ text │ not null
358+-- Our schema consists of two tables - @author@ and @project@. A @project@
359+-- always has an @author@, but not all @author@s have projects. Each @author@
360+-- has a name and (maybe) an associated website, and each project has a name.
362+-- Now that we've seen our schema, we can begin writing a mapping in Rel8. The
363+-- idiomatic way to map a table is to use a record that is parameterised what
364+-- Rel8 calls an /interpretation functor/, and to define each field with
365+-- 'Column'. For this type to be usable with Rel8 we need it to be an instance
366+-- of 'HigherKindedTable', which can be derived with a combination of
367+-- @DeriveAnyClass@ and @DeriveGeneric@.
369+-- Following these steps for @author@, we have:
371+-- > data Author f = Author
372+-- > { authorId :: Column f Int64
373+-- > , name :: Column f Text
374+-- > , url :: Column f (Maybe Text)
375+-- > } deriving (Generic, HigherKindedTable)
377+-- This is a perfectly reasonable definition, but cautious readers might notice
378+-- a problem - in particular, with the type of the @authorId@ field. While
379+-- @Int64@ is correct, it's not the best type. If we had other identifier types
380+-- in our project, it would be too easy to accidentally mix them up and create
381+-- nonsensical joins. As Haskell programmers, we often solve this problem by
382+-- creating @newtype@ wrappers, and we can also use this technique with Rel8:
385+-- newtype AuthorId = AuthorId { toInt64 :: Int64 }
386+-- deriving newtype (DBEq, DBType, Eq, Show)
456−-- * Your SQL table needs a certain text field that tags which Haskell constructor is in use.
389+-- Now we can write our final schema mapping. First, the @author@ table:
457−-- * You have to use a few combinators to specify the sum type's individual constructors.
458−-- * If you want to do case analysis at the @Expr@ (SQL) level, you can use 'maybe'/'either'-like eliminators.
460−-- The documentation in this section will assume a set of database types like this:
391+-- >>> :{
392+-- data Author f = Author
393+-- { authorId :: Column f AuthorId
394+-- , authorName :: Column f Text
395+-- , authorUrl :: Column f (Maybe Text)
397+-- deriving stock Generic
398+-- deriving anyclass HigherKindedTable
462−-- @
401+-- And similarly, the @project@ table:
463−-- data Thing f = ThingEmployer (Employer f) | ThingPotato (Potato f) | Nullary
464−-- deriving stock Generic
466−-- data Employer f = Employer { employerId :: f Int32, employerName :: f Text}
403+-- >>> :{
404+-- data Project f = Project
405+-- { projectAuthorId :: Column f AuthorId
406+-- , projectName :: Column f Text
467-- deriving stock Generic
408-- deriving stock Generic
468−-- deriving anyclass Rel8able
409+-- deriving anyclass HigherKindedTable
470−-- data Potato f = Potato { size :: f Int32, grower :: f Text }
412+-- To show query results in this documentation, we'll also need @Show@
471−-- deriving stock Generic
413+-- instances: Unfortunately these definitions look a bit scary, but they are
472−-- deriving anyclass Rel8able
414+-- essentially just @deriving (Show)@:
474−
416+-- >>> deriving stock instance f ~ Identity => Show (Author f)
475−-- $naming
417+-- >>> deriving stock instance f ~ Identity => Show (Project f)
477−-- First, in your 'TableSchema', name your type like this:
419+-- These data types describe the structural mapping of the tables, but we also
478−--
420+-- need to specify a 'TableSchema' for each table. A @TableSchema@ contains the
479−-- @
421+-- name of the table and the name of all columns in the table, which will
480−-- thingSchema :: TableSchema (ADT Thing Name)
422+-- ultimately allow us to @SELECT@ and @INSERT@ rows for these tables.
481−-- thingSchema =
423+--
482−-- TableSchema
424+-- To define a @TableSchema@, we just need to fill construct appropriate
483−-- { schema = Nothing,
425+-- @TableSchema@ values. When it comes to the @tableColumns@ field, we
484−-- name = \"thing\",
426+-- construct values of our data types above, and set each field to the name of
485−-- columns =
427+-- the column that it maps to:
486−-- nameADT @Thing
428+--
487−-- \"tag\"
429+-- First, @authorSchema@ describes the column names of the @author@ table when
488−-- Employer
430+-- associated with the @Author@ type:
489−-- { employerName = \"name\",
431+--
490−-- employerId = \"id\"
432+-- >>> :{
491−-- }
433+-- authorSchema :: TableSchema (Author ColumnSchema)
492−-- Potato {size = \"size\", grower = \"Mary\"}
434+-- authorSchema = TableSchema
493−-- }
435+-- { tableName = "author"
494−-- @
436+-- , tableSchema = Nothing
495−--
437+-- , tableColumns = Author
496−-- Note that @nameADT \@Thing "tag"@ is variadic: it accepts one
438+-- { authorId = "author_id"
497−-- argument per constructor, except the nullary ones (Nullary) because
439+-- , authorName = "name"
498−-- there's nothing to do for them.
440+-- , authorUrl = "url"
500−-- $deconstruction
442+-- }
502−-- To deconstruct sum types at the SQL level, use 'deconstructADT',
444+--
503−-- which is also variadic, and has one argument for each
445+-- And likewise for @project@ and @Project@:
504−-- constructor. Similar to 'maybe'.
446+--
506−-- @
448+-- projectSchema :: TableSchema (Project ColumnSchema)
507−-- query :: Query (ADT Thing Expr)
449+-- projectSchema = TableSchema
508−-- query = do
450+-- { tableName = "project"
509−-- thingExpr <- each thingSchema
451+-- , tableSchema = Nothing
510−-- where_ $
452+-- , tableColumns = Project
511−-- deconstructADT @Thing
453+-- { projectAuthorId = "author_id"
512−-- (\employer -> employerName employer ==. lit \"Mary\")
454+-- , projectName = "name"
513−-- (\potato -> grower potato ==. lit \"Mary\")
455+-- }
514−-- (lit False) -- Nullary case
456+-- }
515−-- thingExpr
457+-- :}
516−-- pure thingExpr
458+--
517−-- @
459+-- Aside: you might be wondering why this information isn't in the definitions
518−--
460+-- of @Author@ and @Project@ above. Rel8 decouples @TableSchema@ from the data
519−-- SQL output:
461+-- types themselves, as not all tables you define will necessarily have a
520−--
462+-- schema. For example, Rel8 allows you to define helper types to simplify the
521−-- @
463+-- types of queries - these tables only exist at query time, but there is no
522−-- SELECT
464+-- corresponding base table. We'll see more on this idea later!
523−-- CAST("tag0_1" AS text) as "tag",
465+--
524−-- CAST("id1_1" AS int4) as "ThingEmployer/_1/employerId",
466+-- With these table definitions, we can now start writing some queries! To
525−-- CAST("name2_1" AS text) as "ThingEmployer/_1/employerName",
467+-- actually run a query, we'll need a database connection. Rel8 uses the
526−-- CAST("size3_1" AS int4) as "ThingPotato/_1/size",
468+-- @hasql@ library, and for this documentation we'll get the connection string
527−-- CAST("Mary4_1" AS text) as "ThingPotato/_1/grower"
469+-- from the @$TEST_DATABASE_URL@ environment variable.
528−-- FROM (SELECT
470+--
529−-- *
471+-- >>> connectionString <- System.Environment.getEnv "TEST_DATABASE_URL"
530−-- FROM (SELECT
472+-- >>> Right c <- Hasql.Connection.acquire (Data.ByteString.Char8.pack connectionString)
531−-- "tag" as "tag0_1",
473+-- >>> Control.Monad.void $ Hasql.Session.run (Hasql.Session.sql "BEGIN") c
532−-- "id" as "id1_1",
474+
533−-- "name" as "name2_1",
475+-- $guideQueries
534−-- "size" as "size3_1",
476+--
535−-- "Mary" as "Mary4_1"
477+-- First, we'll take a look at @SELECT@ statements - usually the bulk of most
536−-- FROM "thing" as "T1") as "T1"
478+-- database heavy applications.
537−-- WHERE (CASE WHEN ("tag0_1") = (CAST(E'ThingPotato' AS text)) THEN ("Mary4_1") = (CAST(E'Mary' AS text))
479+--
538−-- WHEN ("tag0_1") = (CAST(E'Nullary' AS text)) THEN CAST(FALSE AS bool) ELSE ("name2_1") = (CAST(E'Mary' AS text)) END)) as "T1"
480+-- In Rel8, @SELECT@ statements are built using the 'Query' monad. You can
539−-- @
481+-- think of this monad like the ordinary @[]@ (List) monad - but this isn't
540−
482+-- required knowledge.
541−-- $construction
483+--
542−--
484+-- To start, we'll look at one of the simplest queries possible - a basic
543−-- To construct an ADT, you can use 'buildADT' or 'constructADT'. Consider the following type:
485+-- @SELECT * FROM@ statement. To select all rows from a table, we use 'each',
544−--
486+-- and supply a 'TableSchema'. So to select all @project@ rows, we can write:
546−-- data Task f = Pending | Complete (CompletedTask f)
488+-- >>> :t each projectSchema
547−-- @
489+-- each projectSchema :: Query (Project Expr)
549−-- 'buildADT' is for constructing values of 'Task' in the 'Expr'
491+-- Notice that @each@ gives us a @Query@ that yields @Project Expr@ rows. To
550−-- context. 'buildADT' needs two type-level arguments before its type
492+-- see what this means, let's have a look at a single field of a
551−-- makes any sense. The first argument is the type of the "ADT", which
493+-- @Project Expr@:
552−-- in our case is 'Task'. The second is the name of the constructor we
494+--
553−-- want to use. So that means we have the following possible
495+-- >>> let aProjectExpr = undefined :: Project Expr
554−-- instantiations of 'buildADT' for 'Task':
496+-- >>> :t projectAuthorId aProjectExpr
555−--
497+-- projectAuthorId aProjectExpr :: Expr AuthorId
557−-- > :t buildADT @Task @\"Pending\"
499+-- Recall we defined @projectAuthorId@ as @Column f AuthorId@. Now we have @f@
558−-- buildADT @Task @\"Pending\" :: ADT Task Expr
500+-- is @Expr@, and @Column Expr AuthorId@ reduces to @Expr AuthorId@. We'll see
559−-- > :t buildADT @Task @\"Complete\"
501+-- more about @Expr@ soon, but you can think of @Expr a@ as "SQL expressions of
560−-- buildADT @Task @\"Complete\" :: CompletedTask Expr -> ADT Task Expr
502+-- type @a@".
562−--
504+-- To execute this @Query@, we pass it to 'select':
563−-- Note that as the "Pending" constructor has no fields, @buildADT
505+--
564−-- \@Task \@"Pending"@ is equivalent to @lit Pending@. But @buildADT
506+-- >>> :t select c (each projectSchema)
565−-- \@Task \@"Complete"@ is not the same as @lit . Complete@:
507+-- select c (each projectSchema) :: MonadIO m => m [Project Identity]
567−-- @
509+-- When we @select@ things containing @Expr@s, Rel8 builds a new response table
568−-- > :t lit . Complete
510+-- with the @Identity@ interpretation. This means you'll get back plain Haskell
569−-- lit . Complete :: CompletedTask Result -> ADT Task Expr
511+-- values. Studying @projectAuthorId@ again, we have:
571−--
513+-- >>> let aProjectIdentity = undefined :: Project Identity
572−--
514+-- >>> :t projectAuthorId aProjectIdentity
573−-- Note that the former takes a @CompletedTask Expr@ while the latter
515+-- projectAuthorId aProjectIdentity :: AuthorId
574−-- takes a @CompletedTask Result@. The former is more powerful because
516+--
575−-- you can construct @Task@s using dynamic values coming a database
517+-- Here @Column Identity AuthorId@ reduces to just @AuthorId@, with no
576−-- query.
518+-- wrappping type at all.
578−-- To show what this can look like in SQL, consider:
520+-- Putting this all together, we can run our first query:
580−-- @
522+-- >>> select c (each projectSchema) >>= mapM_ print
581−-- > :{
523+-- Project {projectAuthorId = 1, projectName = "rel8"}
582−-- showQuery $ values
524+-- Project {projectAuthorId = 2, projectName = "aeson"}
583−-- [ buildADT @Task @\"Pending\"
525+-- Project {projectAuthorId = 2, projectName = "text"}
584−-- , buildADT @Task @\"Complete\" CompletedTask {date = Rel8.Expr.Time.now}
526+--
529+-- We now know that 'each' is the equivalent of a @SELECT *@ query, but
530+-- sometimes we're only interested in a subset of the columns of a table. To
531+-- restrict the returned columns, we can specify a projection by using 'Query's
532+-- @Functor@ instance:
534+-- >>> select c $ projectName <$> each projectSchema
535+-- ["rel8","aeson","text"]
539+-- Another common operation in relational databases is to take the @JOIN@ of
540+-- multiple tables. Rel8 doesn't have a specific join operation, but we can
541+-- recover the functionality of a join by selecting all rows of two tables, and
542+-- then using 'where_' to filter them.
544+-- To see how this works, first let's look at taking the product of two tables.
545+-- We can do this by simply calling 'each' twice, and then returning a tuple of
549+-- mapM_ print =<< select c do
550+-- author <- each authorSchema
551+-- project <- each projectSchema
552+-- return (projectName project, authorName author)
555+-- ("rel8","Bryan O'Sullivan")
556+-- ("rel8","Emily Pillmore")
558+-- ("aeson","Bryan O'Sullivan")
559+-- ("aeson","Emily Pillmore")
561+-- ("text","Bryan O'Sullivan")
562+-- ("text","Emily Pillmore")
564+-- This isn't quite right, though, as we have ended up pairing up the wrong
565+-- projects and authors. To fix this, we can use 'where_' to restrict the
566+-- returned rows. We could write:
569+-- > author <- each authorSchema
570+-- > project <- each projectSchema
571+-- > where_ $ projectAuthorId project ==. authorId author
572+-- > return (project, author)
574+-- but doing this every time you need a join can obscure the meaning of the
575+-- query you're writing. A good practice is to introduce specialised functions
576+-- for the particular joins in your database. In our case, this would be:
579+-- projectsForAuthor :: Author Expr -> Query (Project Expr)
580+-- projectsForAuthor a = each projectSchema >>= filter \p ->
581+-- projectAuthorId p ==. authorId a
584+-- Our final query is then:
587+-- mapM_ print =<< select c do
588+-- author <- each authorSchema
589+-- project <- projectsForAuthor author
590+-- return (projectName project, authorName author)
593+-- ("aeson","Bryan O'Sullivan")
594+-- ("text","Bryan O'Sullivan")
598+-- Rel8 is also capable of performing @LEFT JOIN@s. To perform @LEFT JOIN@s, we
599+-- follow the same approach as before, but use the 'optional' query transformer
600+-- to allow for the possibility of the join to fail.
602+-- In our test database, we can see that there's another author who we haven't
605+-- >>> select c $ authorName <$> each authorSchema
606+-- ["Ollie","Bryan O'Sullivan","Emily Pillmore"]
608+-- Emily wasn't returned in our earlier query because - in our database - she
609+-- doesn't have any registered projects. We can account for this partiality in
610+-- our original query by wrapping the @projectsForAuthor@ call with 'optional':
613+-- mapM_ print =<< select c do
614+-- author <- each authorSchema
615+-- mproject <- optional $ projectsForAuthor author
616+-- return (authorName author, projectName <$> mproject)
618+-- ("Ollie",Just "rel8")
619+-- ("Bryan O'Sullivan",Just "aeson")
620+-- ("Bryan O'Sullivan",Just "text")
621+-- ("Emily Pillmore",Nothing)
625+-- Aggregations are operations like @sum@ and @count@ - operations that reduce
626+-- multiple rows to single values. To perform aggregations in Rel8, we can use
627+-- the 'aggregate' function, which takes a 'Query' of aggregated expressions,
628+-- runs the aggregation, and returns aggregated rows.
630+-- To start, let's look at a simple aggregation that tells us how many projects
635+-- Rel8 is also capable of aggregating multiple rows into a single row by
636+-- concatenating all rows as a list. This aggregation allows us to break free
637+-- of the row-orientated nature of SQL and write queries that return tree-like
638+-- structures. Earlier we saw an example of returning authors with their
639+-- projects, but the query didn't do a great job of describing the one-to-many
640+-- relationship between authors and their projects.
642+-- Let's look again at a query that returns authors and their projects, and
643+-- focus on the /type/ of that query.
646+-- projectsForAuthor a = each projectSchema >>= filter \p ->
647+-- projectAuthorId p ==. authorId a
651+-- let authorsAndProjects = do
652+-- author <- each authorSchema
653+-- project <- projectsForAuthor author
654+-- return (author, project)
658+-- >>> :t select c authorsAndProjects
659+-- select c authorsAndProjects
660+-- :: MonadIO m => m [(Author Identity, Project Identity)]
662+-- Our query gives us a single list of pairs of authors and projects. However,
663+-- with our domain knowledge of the schema, this isn't a great type - what we'd
664+-- rather have is a list of pairs of authors and /lists/ of projects. That is,
665+-- what we'd like is:
667+-- > [(Author Identity, [Project Identity])]
669+-- This would be a much better type! Rel8 can produce a query with this type by
670+-- simply wrapping the call to @projectsForAuthor@ with either 'some' or
671+-- 'many'. Here we'll use 'many', which allows for the possibility of an author
672+-- to have no projects:
675+-- mapM_ print =<< select c do
676+-- author <- each authorSchema
677+-- projectNames <- many $ projectName <$> projectsForAuthor author
678+-- return (authorName author, projectNames)
587−-- @
680+-- ("Ollie",["rel8"])
588−--
681+-- ("Bryan O'Sullivan",["aeson","text"])
589−-- This produces the following SQL:
682+-- ("Emily Pillmore",[])
593−-- CAST(\"values0_1\" AS text) as \"tag\",
594−-- CAST(\"values1_1\" AS timestamptz) as \"Complete/_1/date\"
597−-- FROM (SELECT \"column1\" as \"values0_1\",
598−-- \"column2\" as \"values1_1\"
601−-- (CAST(E'Pending' AS text),CAST(NULL AS timestamptz)),
602−-- (CAST(E'Complete' AS text),CAST(now() AS timestamptz))) as \"V\") as \"T1\") as \"T1\"
605−-- This is what you get if you run it in @psql@:
609−-- tag | Complete/_1/date
610−-- ----------+-------------------------------
612−-- Complete | 2022-05-19 21:28:23.969065+00
616−-- "constructADT" is less convenient but more general alternative to
617−-- "buildADT". It requires only one type-level argument for its type
621−-- > :t constructADT @Task
622−-- constructADT @Task
623−-- :: (forall r. r -> (CompletedTask Expr -> r) -> r) -> ADT Task Expr
626−-- This might still seem a bit opaque, but basically it gives you a
627−-- Church-encoded constructor for arbitrary algebraic data types. You
628−-- might use it as follows:
632−-- pending :: ADT Task Expr
633−-- pending = constructADT @Task $ \pending _complete -> pending
635−-- complete :: ADT Task Expr
636−-- complete = constructADT @Task $ \_pending complete -> complete CompletedTask {date = Rel8.Expr.Time.now}
639−-- These values are otherwise identical to the ones we saw above with
640−-- @buildADT@, it's just a different style of constructing them.
645−-- 1. Note that the order of the arguments for all of these functions
646−-- is determined by the order of the constructors in the data
647−-- definition. If it were @data Task = Complete (CompletedTask f) |
648−-- Pending@ then the order of all the invocations of @constructADT@
649−-- and @deconstructADT@ would need to change.
651−-- 2. Maybe this is obvious, but just to spell it out: once you're in
652−-- the @Result@ context, you can of course construct @Task@ values
653−-- normally and use standard Haskell pattern-matching. @constructADT@
654−-- and @deconstructADT@ are specifically only needed in the @Expr@
655−-- context, and they allow you to do the equivalent of pattern
656−-- matching in PostgreSQL.