@@ -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
35− , Column
42+ , Column, Field, Necessity( Required, Optional )
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,364 @@ 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)
445−
325+import Rel8.Kind.Necessity
446−
326+import Rel8.Schema.Field (Field)
448−-- To run queries and otherwise interact with a PostgreSQL database, Rel8
328+
449−-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
329+-- $setup
450−-- 'insert', 'update' and 'delete' will generally need the
330+--
451−-- `DuplicateRecordFields` language extension enabled.
331+-- In this section, we'll take a look at using Rel8 to work with a small
452−
332+-- database for Haskell packages. We'll take a look at idiomatic usage of Rel8,
453−-- $adts
333+-- mapping tables to Haskell, and then look at writing some simple queries.
454−-- Algebraic data types can be modelled between Haskell and SQL.
334+--
335+-- The documentation in this library uses the following language extensions:
337+-- >>> :set -XBlockArguments
338+-- >>> :set -XDeriveAnyClass
339+-- >>> :set -XDeriveGeneric
340+-- >>> :set -XDerivingStrategies
341+-- >>> :set -XDerivingVia
342+-- >>> :set -XDuplicateRecordFields
343+-- >>> :set -XGeneralizedNewtypeDeriving
344+-- >>> :set -XOverloadedStrings
345+-- >>> :set -XStandaloneDeriving
346+-- >>> :set -XTypeApplications
347+-- >>> :set -XTypeFamilies
349+-- Before we start writing any Haskell, let's take a look at the schema we'll
352+-- > author: project:
354+-- > Column │ Type │ Nullable Column │ Type │ Nullable
355+-- > ═══════════╪═════════╪══════════ ═══════════╪═════════╪══════════
356+-- > author_id │ integer │ not null author_id │ integer │ not null
357+-- > name │ text │ not null name │ text │ not null
360+-- Our schema consists of two tables - @author@ and @project@. A @project@
361+-- always has an @author@, but not all @author@s have projects. Each @author@
362+-- has a name and (maybe) an associated website, and each project has a name.
364+-- Now that we've seen our schema, we can begin writing a mapping in Rel8. The
365+-- idiomatic way to map a table is to use a record that is parameterised what
366+-- Rel8 calls an /interpretation functor/, and to define each field with
367+-- 'Column'. For this type to be usable with Rel8 we need it to be an instance
368+-- of 'HigherKindedTable', which can be derived with a combination of
369+-- @DeriveAnyClass@ and @DeriveGeneric@.
371+-- Following these steps for @author@, we have:
373+-- > data Author f = Author
374+-- > { authorId :: Column f Int64
375+-- > , name :: Column f Text
376+-- > , url :: Column f (Maybe Text)
377+-- > } deriving (Generic, HigherKindedTable)
379+-- This is a perfectly reasonable definition, but cautious readers might notice
380+-- a problem - in particular, with the type of the @authorId@ field. While
381+-- @Int64@ is correct, it's not the best type. If we had other identifier types
382+-- in our project, it would be too easy to accidentally mix them up and create
383+-- nonsensical joins. As Haskell programmers, we often solve this problem by
384+-- creating @newtype@ wrappers, and we can also use this technique with Rel8:
387+-- newtype AuthorId = AuthorId { toInt64 :: Int64 }
388+-- deriving newtype (DBEq, DBType, Eq, Show)
456−-- * Your SQL table needs a certain text field that tags which Haskell constructor is in use.
391+-- 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:
393+-- >>> :{
394+-- data Author f = Author
395+-- { authorId :: Column f AuthorId
396+-- , authorName :: Column f Text
397+-- , authorUrl :: Column f (Maybe Text)
399+-- deriving stock Generic
400+-- deriving anyclass HigherKindedTable
462−-- @
403+-- 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}
405+-- >>> :{
406+-- data Project f = Project
407+-- { projectAuthorId :: Column f AuthorId
408+-- , projectName :: Column f Text
467-- deriving stock Generic
410-- deriving stock Generic
468−-- deriving anyclass Rel8able
411+-- deriving anyclass HigherKindedTable
470−-- data Potato f = Potato { size :: f Int32, grower :: f Text }
414+-- To show query results in this documentation, we'll also need @Show@
471−-- deriving stock Generic
415+-- instances: Unfortunately these definitions look a bit scary, but they are
472−-- deriving anyclass Rel8able
416+-- essentially just @deriving (Show)@:
474−
418+-- >>> deriving stock instance f ~ Identity => Show (Author f)
475−-- $naming
419+-- >>> deriving stock instance f ~ Identity => Show (Project f)
421+-- These data types describe the structural mapping of the tables, but we also
422+-- need to specify a 'TableSchema' for each table. A @TableSchema@ contains the
423+-- name of the table and the name of all columns in the table, which will
424+-- ultimately allow us to @SELECT@ and @INSERT@ rows for these tables.
426+-- To define a @TableSchema@, we just need to fill construct appropriate
427+-- @TableSchema@ values. When it comes to the @tableColumns@ field, we
428+-- construct values of our data types above, and set each field to the name of
429+-- the column that it maps to:
431+-- First, @authorSchema@ describes the column names of the @author@ table when
432+-- associated with the @Author@ type:
435+-- authorSchema :: TableSchema (Author ColumnSchema)
436+-- authorSchema = TableSchema
437+-- { tableName = "author"
438+-- , tableSchema = Nothing
439+-- , tableColumns = Author
440+-- { authorId = "author_id"
441+-- , authorName = "name"
442+-- , authorUrl = "url"
447+-- And likewise for @project@ and @Project@:
450+-- projectSchema :: TableSchema (Project ColumnSchema)
451+-- projectSchema = TableSchema
452+-- { tableName = "project"
453+-- , tableSchema = Nothing
454+-- , tableColumns = Project
455+-- { projectAuthorId = "author_id"
456+-- , projectName = "name"
461+-- Aside: you might be wondering why this information isn't in the definitions
462+-- of @Author@ and @Project@ above. Rel8 decouples @TableSchema@ from the data
463+-- types themselves, as not all tables you define will necessarily have a
464+-- schema. For example, Rel8 allows you to define helper types to simplify the
465+-- types of queries - these tables only exist at query time, but there is no
466+-- corresponding base table. We'll see more on this idea later!
468+-- With these table definitions, we can now start writing some queries! To
469+-- actually run a query, we'll need a database connection. Rel8 uses the
470+-- @hasql@ library, and for this documentation we'll get the connection string
471+-- from the @$TEST_DATABASE_URL@ environment variable.
477−-- First, in your 'TableSchema', name your type like this:
473+-- >>> connectionString <- System.Environment.getEnv "TEST_DATABASE_URL"
478−--
474+-- >>> Right c <- Hasql.Connection.acquire (Data.ByteString.Char8.pack connectionString)
475+-- >>> Control.Monad.void $ Hasql.Session.run (Hasql.Session.sql "BEGIN") c
479+-- First, we'll take a look at @SELECT@ statements - usually the bulk of most
480+-- database heavy applications.
482+-- In Rel8, @SELECT@ statements are built using the 'Query' monad. You can
483+-- think of this monad like the ordinary @[]@ (List) monad - but this isn't
484+-- required knowledge.
486+-- To start, we'll look at one of the simplest queries possible - a basic
487+-- @SELECT * FROM@ statement. To select all rows from a table, we use 'each',
488+-- and supply a 'TableSchema'. So to select all @project@ rows, we can write:
490+-- >>> :t each projectSchema
491+-- each projectSchema :: Query (Project Expr)
493+-- Notice that @each@ gives us a @Query@ that yields @Project Expr@ rows. To
494+-- see what this means, let's have a look at a single field of a
497+-- >>> let aProjectExpr = undefined :: Project Expr
498+-- >>> :t projectAuthorId aProjectExpr
499+-- projectAuthorId aProjectExpr :: Expr AuthorId
501+-- Recall we defined @projectAuthorId@ as @Column f AuthorId@. Now we have @f@
502+-- is @Expr@, and @Column Expr AuthorId@ reduces to @Expr AuthorId@. We'll see
503+-- more about @Expr@ soon, but you can think of @Expr a@ as "SQL expressions of
506+-- To execute this @Query@, we pass it to 'select':
508+-- >>> :t select c (each projectSchema)
509+-- select c (each projectSchema) :: MonadIO m => m [Project Identity]
511+-- When we @select@ things containing @Expr@s, Rel8 builds a new response table
512+-- with the @Identity@ interpretation. This means you'll get back plain Haskell
513+-- values. Studying @projectAuthorId@ again, we have:
515+-- >>> let aProjectIdentity = undefined :: Project Identity
516+-- >>> :t projectAuthorId aProjectIdentity
517+-- projectAuthorId aProjectIdentity :: AuthorId
519+-- Here @Column Identity AuthorId@ reduces to just @AuthorId@, with no
520+-- wrappping type at all.
522+-- Putting this all together, we can run our first query:
524+-- >>> select c (each projectSchema) >>= mapM_ print
525+-- Project {projectAuthorId = 1, projectName = "rel8"}
526+-- Project {projectAuthorId = 2, projectName = "aeson"}
527+-- Project {projectAuthorId = 2, projectName = "text"}
531+-- We now know that 'each' is the equivalent of a @SELECT *@ query, but
532+-- sometimes we're only interested in a subset of the columns of a table. To
533+-- restrict the returned columns, we can specify a projection by using 'Query's
534+-- @Functor@ instance:
480−-- thingSchema :: TableSchema (ADT Thing Name)
536+-- >>> select c $ projectName <$> each projectSchema
481−-- thingSchema =
537+-- ["rel8","aeson","text"]
483−-- { schema = Nothing,
539+-- $guideJoins
484−-- name = \"thing\",
540+--
485−-- columns =
541+-- Another common operation in relational databases is to take the @JOIN@ of
486−-- nameADT @Thing
542+-- multiple tables. Rel8 doesn't have a specific join operation, but we can
487−-- \"tag\"
543+-- recover the functionality of a join by selecting all rows of two tables, and
488−-- Employer
544+-- then using 'where_' to filter them.
489−-- { employerName = \"name\",
490−-- employerId = \"id\"
492−-- Potato {size = \"size\", grower = \"Mary\"}
496−-- Note that @nameADT \@Thing "tag"@ is variadic: it accepts one
497−-- argument per constructor, except the nullary ones (Nullary) because
498−-- there's nothing to do for them.
502−-- To deconstruct sum types at the SQL level, use 'deconstructADT',
546+-- To see how this works, first let's look at taking the product of two tables.
503−-- which is also variadic, and has one argument for each
547+-- We can do this by simply calling 'each' twice, and then returning a tuple of
504−-- constructor. Similar to 'maybe'.
548+-- their results.
507−-- query :: Query (ADT Thing Expr)
551+-- mapM_ print =<< select c do
508−-- query = do
552+-- author <- each authorSchema
509−-- thingExpr <- each thingSchema
553+-- project <- each projectSchema
510−-- where_ $
554+-- return (projectName project, authorName author)
511−-- deconstructADT @Thing
555+-- :}
512−-- (\employer -> employerName employer ==. lit \"Mary\")
556+-- ("rel8","Ollie")
513−-- (\potato -> grower potato ==. lit \"Mary\")
557+-- ("rel8","Bryan O'Sullivan")
514−-- (lit False) -- Nullary case
558+-- ("rel8","Emily Pillmore")
515−-- thingExpr
559+-- ("aeson","Ollie")
516−-- pure thingExpr
560+-- ("aeson","Bryan O'Sullivan")
517−-- @
561+-- ("aeson","Emily Pillmore")
518−--
562+-- ("text","Ollie")
519−-- SQL output:
563+-- ("text","Bryan O'Sullivan")
520−--
564+-- ("text","Emily Pillmore")
522−-- SELECT
566+-- This isn't quite right, though, as we have ended up pairing up the wrong
523−-- CAST("tag0_1" AS text) as "tag",
567+-- projects and authors. To fix this, we can use 'where_' to restrict the
524−-- CAST("id1_1" AS int4) as "ThingEmployer/_1/employerId",
568+-- returned rows. We could write:
525−-- CAST("name2_1" AS text) as "ThingEmployer/_1/employerName",
526−-- CAST("size3_1" AS int4) as "ThingPotato/_1/size",
527−-- CAST("Mary4_1" AS text) as "ThingPotato/_1/grower"
530−-- FROM (SELECT
570+-- > select c $ do
531−-- "tag" as "tag0_1",
571+-- > author <- each authorSchema
532−-- "id" as "id1_1",
572+-- > project <- each projectSchema
533−-- "name" as "name2_1",
573+-- > where_ $ projectAuthorId project ==. authorId author
534−-- "size" as "size3_1",
574+-- > return (project, author)
535−-- "Mary" as "Mary4_1"
575+--
536−-- FROM "thing" as "T1") as "T1"
576+-- but doing this every time you need a join can obscure the meaning of the
537−-- WHERE (CASE WHEN ("tag0_1") = (CAST(E'ThingPotato' AS text)) THEN ("Mary4_1") = (CAST(E'Mary' AS text))
577+-- query you're writing. A good practice is to introduce specialised functions
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"
578+-- for the particular joins in your database. In our case, this would be:
541−-- $construction
581+-- projectsForAuthor :: Author Expr -> Query (Project Expr)
582+-- projectsForAuthor a = each projectSchema >>= filter \p ->
583+-- projectAuthorId p ==. authorId a
543−-- To construct an ADT, you can use 'buildADT' or 'constructADT'. Consider the following type:
586+-- Our final query is then:
546−-- data Task f = Pending | Complete (CompletedTask f)
589+-- mapM_ print =<< select c do
590+-- author <- each authorSchema
591+-- project <- projectsForAuthor author
592+-- return (projectName project, authorName author)
595+-- ("aeson","Bryan O'Sullivan")
596+-- ("text","Bryan O'Sullivan")
549−-- 'buildADT' is for constructing values of 'Task' in the 'Expr'
600+-- Rel8 is also capable of performing @LEFT JOIN@s. To perform @LEFT JOIN@s, we
550−-- context. 'buildADT' needs two type-level arguments before its type
601+-- follow the same approach as before, but use the 'optional' query transformer
551−-- makes any sense. The first argument is the type of the "ADT", which
602+-- to allow for the possibility of the join to fail.
552−-- in our case is 'Task'. The second is the name of the constructor we
603+--
553−-- want to use. So that means we have the following possible
604+-- In our test database, we can see that there's another author who we haven't
554−-- instantiations of 'buildADT' for 'Task':
605+-- seen yet:
607+-- >>> select c $ authorName <$> each authorSchema
608+-- ["Ollie","Bryan O'Sullivan","Emily Pillmore"]
610+-- Emily wasn't returned in our earlier query because - in our database - she
611+-- doesn't have any registered projects. We can account for this partiality in
612+-- our original query by wrapping the @projectsForAuthor@ call with 'optional':
615+-- mapM_ print =<< select c do
616+-- author <- each authorSchema
617+-- mproject <- optional $ projectsForAuthor author
618+-- return (authorName author, projectName <$> mproject)
556−-- @
620+-- ("Ollie",Just "rel8")
557−-- > :t buildADT @Task @\"Pending\"
621+-- ("Bryan O'Sullivan",Just "aeson")
558−-- buildADT @Task @\"Pending\" :: ADT Task Expr
622+-- ("Bryan O'Sullivan",Just "text")
559−-- > :t buildADT @Task @\"Complete\"
623+-- ("Emily Pillmore",Nothing)
560−-- buildADT @Task @\"Complete\" :: CompletedTask Expr -> ADT Task Expr
624+
561−-- @
625+-- $guideAggregation
563−-- Note that as the "Pending" constructor has no fields, @buildADT
627+-- Aggregations are operations like @sum@ and @count@ - operations that reduce
564−-- \@Task \@"Pending"@ is equivalent to @lit Pending@. But @buildADT
628+-- multiple rows to single values. To perform aggregations in Rel8, we can use
565−-- \@Task \@"Complete"@ is not the same as @lit . Complete@:
629+-- the 'aggregate' function, which takes a 'Query' of aggregated expressions,
630+-- runs the aggregation, and returns aggregated rows.
567−-- @
632+-- To start, let's look at a simple aggregation that tells us how many projects
568−-- > :t lit . Complete
633+-- exist:
569−-- lit . Complete :: CompletedTask Result -> ADT Task Expr
571−--
635+-- >>> error "TODO"
573−-- Note that the former takes a @CompletedTask Expr@ while the latter
637+-- Rel8 is also capable of aggregating multiple rows into a single row by
574−-- takes a @CompletedTask Result@. The former is more powerful because
638+-- concatenating all rows as a list. This aggregation allows us to break free
575−-- you can construct @Task@s using dynamic values coming a database
639+-- of the row-orientated nature of SQL and write queries that return tree-like
576−-- query.
640+-- structures. Earlier we saw an example of returning authors with their
577−--
641+-- projects, but the query didn't do a great job of describing the one-to-many
578−-- To show what this can look like in SQL, consider:
642+-- relationship between authors and their projects.
644+-- Let's look again at a query that returns authors and their projects, and
645+-- focus on the /type/ of that query.
648+-- projectsForAuthor a = each projectSchema >>= filter \p ->
649+-- projectAuthorId p ==. authorId a
582−-- showQuery $ values
653+-- let authorsAndProjects = do
583−-- [ buildADT @Task @\"Pending\"
654+-- author <- each authorSchema
584−-- , buildADT @Task @\"Complete\" CompletedTask {date = Rel8.Expr.Time.now}
655+-- project <- projectsForAuthor author
585−-- ]
656+-- return (author, project)
660+-- >>> :t select c authorsAndProjects
661+-- select c authorsAndProjects
662+-- :: MonadIO m => m [(Author Identity, Project Identity)]
664+-- Our query gives us a single list of pairs of authors and projects. However,
665+-- with our domain knowledge of the schema, this isn't a great type - what we'd
666+-- rather have is a list of pairs of authors and /lists/ of projects. That is,
667+-- what we'd like is:
669+-- > [(Author Identity, [Project Identity])]
671+-- This would be a much better type! Rel8 can produce a query with this type by
672+-- simply wrapping the call to @projectsForAuthor@ with either 'some' or
673+-- 'many'. Here we'll use 'many', which allows for the possibility of an author
674+-- to have no projects:
677+-- mapM_ print =<< select c do
678+-- author <- each authorSchema
679+-- projectNames <- many $ projectName <$> projectsForAuthor author
680+-- return (authorName author, projectNames)
587−-- @
682+-- ("Ollie",["rel8"])
588−--
683+-- ("Bryan O'Sullivan",["aeson","text"])
589−-- This produces the following SQL:
684+-- ("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.