@@ -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,13 @@ module Rel8
249 -- ** Aggregation
183 -- ** Aggregation
250 , Aggregate
184 , Aggregate
252 , aggregate
185 , aggregate
255 , listAgg, listAggExpr
186 , listAgg, listAggExpr
257 , nonEmptyAgg, nonEmptyAggExpr
187 , nonEmptyAgg, nonEmptyAggExpr
258 , DBMax, max
189 , DBMax, max
259 , DBMin, min
190 , DBMin, min
260− , DBSum, sum, sumWhere, avg
191+ , DBSum, sum, sumWhere
261 , DBString, stringAgg
192 , DBString, stringAgg
263 , countStar
194 , countStar
@@ -266,6 +197,11 @@ module Rel8
202+ , distributeAggregate
269 -- ** Ordering
205 -- ** Ordering
270 , orderBy
206 , orderBy
@@ -274,71 +210,43 @@ module Rel8
274 , nullsFirst
210 , nullsFirst
275 , nullsLast
211 , nullsLast
277− -- ** Window functions
304 , Serializable
214 , Serializable
308 -- * Running statements
216 -- * Running statements
311 -- ** @SELECT@
217 -- ** @SELECT@
314 -- ** @INSERT@
220 -- ** @INSERT@
315 , Insert(..)
221 , Insert(..)
316 , OnConflict(..)
222 , OnConflict(..)
319− , unsafeDefault
224+ , toInsert
320− , showInsert
225+ , toInsertDefaults
322 -- ** @DELETE@
227 -- ** @DELETE@
323 , Delete(..)
228 , Delete(..)
327 -- ** @UPDATE@
231 -- ** @UPDATE@
330− , showUpdate
233+ , Update(..)
332 -- ** @.. RETURNING@
235 -- ** @.. RETURNING@
333 , Returning(..)
236 , Returning(..)
335 -- ** @CREATE VIEW@
238 -- ** @CREATE VIEW@
336 , createView
239 , createView
337− , createOrReplaceView
339− -- ** Sequences
241+ -- * TODO
340− , nextval
242+ -- TODO
341− , evaluate
243+ -- These need organizing, but are reachable from Rel8's documentation so we
244+ -- do need to export and document them.
@@ -346,94 +254,65 @@ import Prelude ()
348import Rel8.Aggregate
256import 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
257import Rel8.Expr
359import Rel8.Expr.Aggregate
258import Rel8.Expr.Aggregate
360import Rel8.Expr.Bool
259import Rel8.Expr.Bool
361−import Rel8.Expr.Default
362import Rel8.Expr.Eq
260import Rel8.Expr.Eq
363import Rel8.Expr.Function
261import Rel8.Expr.Function
364import Rel8.Expr.Null
262import Rel8.Expr.Null
365−import Rel8.Expr.Opaleye (unsafeCastExpr, unsafeLiteral)
366import Rel8.Expr.Ord
263import Rel8.Expr.Ord
367import Rel8.Expr.Order
264import Rel8.Expr.Order
368import Rel8.Expr.Serialize
265import 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
266import Rel8.Order
374import Rel8.Query
267import Rel8.Query
375import Rel8.Query.Aggregate
268import Rel8.Query.Aggregate
376import Rel8.Query.Distinct
269import Rel8.Query.Distinct
377import Rel8.Query.Each
270import Rel8.Query.Each
378import Rel8.Query.Either
271import Rel8.Query.Either
379−import Rel8.Query.Evaluate
380import Rel8.Query.Exists
272import Rel8.Query.Exists
381import Rel8.Query.Filter
273import Rel8.Query.Filter
382−import Rel8.Query.Indexed
383import Rel8.Query.Limit
274import Rel8.Query.Limit
384import Rel8.Query.List
275import Rel8.Query.List
385import Rel8.Query.Maybe
276import Rel8.Query.Maybe
386import Rel8.Query.Null
277import Rel8.Query.Null
387import Rel8.Query.Order
278import Rel8.Query.Order
388−import Rel8.Query.Rebind
389import Rel8.Query.SQL (showQuery)
279import Rel8.Query.SQL (showQuery)
390import Rel8.Query.Set
280import Rel8.Query.Set
391import Rel8.Query.These
281import Rel8.Query.These
392import Rel8.Query.Values
282import Rel8.Query.Values
393−import Rel8.Query.Window
283+import Rel8.Schema.Column
284+import Rel8.Schema.Context.Label
394−import Rel8.Schema.Field
285+import Rel8.Schema.Generic
395import Rel8.Schema.HTable
286import Rel8.Schema.HTable
396import Rel8.Schema.Name
287import Rel8.Schema.Name
397−import Rel8.Schema.Null hiding ( nullable )
288+import Rel8.Schema.Nullability
398−import Rel8.Schema.Result ( Result )
399import Rel8.Schema.Table
289import Rel8.Schema.Table
400import Rel8.Statement.Delete
290import Rel8.Statement.Delete
401import Rel8.Statement.Insert
291import Rel8.Statement.Insert
402−import Rel8.Statement.OnConflict
403import Rel8.Statement.Returning
292import Rel8.Statement.Returning
404import Rel8.Statement.Select
293import Rel8.Statement.Select
405−import Rel8.Statement.SQL
406import Rel8.Statement.Update
294import Rel8.Statement.Update
407import Rel8.Statement.View
295import Rel8.Statement.View
408import Rel8.Table
296import Rel8.Table
409−import Rel8.Table.ADT
410import Rel8.Table.Aggregate
297import Rel8.Table.Aggregate
411import Rel8.Table.Alternative
298import Rel8.Table.Alternative
412import Rel8.Table.Bool
299import Rel8.Table.Bool
413import Rel8.Table.Either
300import Rel8.Table.Either
414import Rel8.Table.Eq
301import Rel8.Table.Eq
415−import Rel8.Table.HKD
302+import Rel8.Table.Insert
416import Rel8.Table.List
303import Rel8.Table.List
417import Rel8.Table.Maybe
304import Rel8.Table.Maybe
418import Rel8.Table.Name
305import Rel8.Table.Name
419import Rel8.Table.NonEmpty
306import Rel8.Table.NonEmpty
420−import Rel8.Table.Null
421−import Rel8.Table.Opaleye ( castTable )
422import Rel8.Table.Ord
307import Rel8.Table.Ord
423import Rel8.Table.Order
308import Rel8.Table.Order
424−import Rel8.Table.Projection
425−import Rel8.Table.Rel8able ()
426import Rel8.Table.Serialize
309import Rel8.Table.Serialize
427import Rel8.Table.These
310import Rel8.Table.These
428−import Rel8.Table.Transpose
429−import Rel8.Table.Window
430import Rel8.Type
311import Rel8.Type
431−import Rel8.Type.Composite
432import Rel8.Type.Eq
312import Rel8.Type.Eq
433−import Rel8.Type.Enum
434import Rel8.Type.Information
313import Rel8.Type.Information
435−import Rel8.Type.JSONBEncoded
436import Rel8.Type.JSONEncoded
314import Rel8.Type.JSONEncoded
315+import Rel8.Type.JSONBEncoded
437import Rel8.Type.Monoid
316import Rel8.Type.Monoid
438import Rel8.Type.Num
317import Rel8.Type.Num
439import Rel8.Type.Ord
318import Rel8.Type.Ord
@@ -441,216 +320,362 @@ import Rel8.Type.ReadShow
441import Rel8.Type.Semigroup
320import Rel8.Type.Semigroup
442import Rel8.Type.String
321import Rel8.Type.String
443import Rel8.Type.Sum
322import Rel8.Type.Sum
444−import Rel8.Window
323+import Rel8.Expr.Opaleye (unsafeCastExpr)
447−-- $running
326+-- $setup
448−-- To run queries and otherwise interact with a PostgreSQL database, Rel8
327+--
449−-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
328+-- 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
329+-- database for Haskell packages. We'll take a look at idiomatic usage of Rel8,
451−-- `DuplicateRecordFields` language extension enabled.
330+-- mapping tables to Haskell, and then look at writing some simple queries.
453−-- $adts
332+-- The documentation in this library uses the following language extensions:
454−-- Algebraic data types can be modelled between Haskell and SQL.
333+--
334+-- >>> :set -XBlockArguments
335+-- >>> :set -XDeriveAnyClass
336+-- >>> :set -XDeriveGeneric
337+-- >>> :set -XDerivingStrategies
338+-- >>> :set -XDerivingVia
339+-- >>> :set -XDuplicateRecordFields
340+-- >>> :set -XGeneralizedNewtypeDeriving
341+-- >>> :set -XOverloadedStrings
342+-- >>> :set -XStandaloneDeriving
343+-- >>> :set -XTypeApplications
344+-- >>> :set -XTypeFamilies
346+-- Before we start writing any Haskell, let's take a look at the schema we'll
349+-- > author: project:
351+-- > Column │ Type │ Nullable Column │ Type │ Nullable
352+-- > ═══════════╪═════════╪══════════ ═══════════╪═════════╪══════════
353+-- > author_id │ integer │ not null author_id │ integer │ not null
354+-- > name │ text │ not null name │ text │ not null
357+-- Our schema consists of two tables - @author@ and @project@. A @project@
358+-- always has an @author@, but not all @author@s have projects. Each @author@
359+-- has a name and (maybe) an associated website, and each project has a name.
361+-- Now that we've seen our schema, we can begin writing a mapping in Rel8. The
362+-- idiomatic way to map a table is to use a record that is parameterised what
363+-- Rel8 calls an /interpretation functor/, and to define each field with
364+-- 'Column'. For this type to be usable with Rel8 we need it to be an instance
365+-- of 'HigherKindedTable', which can be derived with a combination of
366+-- @DeriveAnyClass@ and @DeriveGeneric@.
368+-- Following these steps for @author@, we have:
370+-- > data Author f = Author
371+-- > { authorId :: Column f Int64
372+-- > , name :: Column f Text
373+-- > , url :: Column f (Maybe Text)
374+-- > } deriving (Generic, HigherKindedTable)
376+-- This is a perfectly reasonable definition, but cautious readers might notice
377+-- a problem - in particular, with the type of the @authorId@ field. While
378+-- @Int64@ is correct, it's not the best type. If we had other identifier types
379+-- in our project, it would be too easy to accidentally mix them up and create
380+-- nonsensical joins. As Haskell programmers, we often solve this problem by
381+-- creating @newtype@ wrappers, and we can also use this technique with Rel8:
384+-- newtype AuthorId = AuthorId { toInt64 :: Int64 }
385+-- deriving newtype (DBEq, DBType, Eq, Show)
456−-- * Your SQL table needs a certain text field that tags which Haskell constructor is in use.
388+-- 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:
390+-- >>> :{
391+-- data Author f = Author
392+-- { authorId :: Column f AuthorId
393+-- , authorName :: Column f Text
394+-- , authorUrl :: Column f (Maybe Text)
396+-- deriving stock Generic
397+-- deriving anyclass HigherKindedTable
462−-- @
400+-- 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}
402+-- >>> :{
403+-- data Project f = Project
404+-- { projectAuthorId :: Column f AuthorId
405+-- , projectName :: Column f Text
467-- deriving stock Generic
407-- deriving stock Generic
468−-- deriving anyclass Rel8able
408+-- deriving anyclass HigherKindedTable
470−-- data Potato f = Potato { size :: f Int32, grower :: f Text }
411+-- To show query results in this documentation, we'll also need @Show@
471−-- deriving stock Generic
412+-- instances: Unfortunately these definitions look a bit scary, but they are
472−-- deriving anyclass Rel8able
413+-- essentially just @deriving (Show)@:
474−
415+-- >>> deriving stock instance f ~ Identity => Show (Author f)
475−-- $naming
416+-- >>> deriving stock instance f ~ Identity => Show (Project f)
418+-- These data types describe the structural mapping of the tables, but we also
419+-- need to specify a 'TableSchema' for each table. A @TableSchema@ contains the
420+-- name of the table and the name of all columns in the table, which will
421+-- ultimately allow us to @SELECT@ and @INSERT@ rows for these tables.
423+-- To define a @TableSchema@, we just need to fill construct appropriate
424+-- @TableSchema@ values. When it comes to the @tableColumns@ field, we
425+-- construct values of our data types above, and set each field to the name of
426+-- the column that it maps to:
428+-- First, @authorSchema@ describes the column names of the @author@ table when
429+-- associated with the @Author@ type:
432+-- authorSchema :: TableSchema (Author ColumnSchema)
433+-- authorSchema = TableSchema
434+-- { tableName = "author"
435+-- , tableSchema = Nothing
436+-- , tableColumns = Author
437+-- { authorId = "author_id"
438+-- , authorName = "name"
439+-- , authorUrl = "url"
444+-- And likewise for @project@ and @Project@:
447+-- projectSchema :: TableSchema (Project ColumnSchema)
448+-- projectSchema = TableSchema
449+-- { tableName = "project"
450+-- , tableSchema = Nothing
451+-- , tableColumns = Project
452+-- { projectAuthorId = "author_id"
453+-- , projectName = "name"
458+-- Aside: you might be wondering why this information isn't in the definitions
459+-- of @Author@ and @Project@ above. Rel8 decouples @TableSchema@ from the data
460+-- types themselves, as not all tables you define will necessarily have a
461+-- schema. For example, Rel8 allows you to define helper types to simplify the
462+-- types of queries - these tables only exist at query time, but there is no
463+-- corresponding base table. We'll see more on this idea later!
465+-- With these table definitions, we can now start writing some queries! To
466+-- actually run a query, we'll need a database connection. Rel8 uses the
467+-- @hasql@ library, and for this documentation we'll get the connection string
468+-- from the @$TEST_DATABASE_URL@ environment variable.
477−-- First, in your 'TableSchema', name your type like this:
470+-- >>> connectionString <- System.Environment.getEnv "TEST_DATABASE_URL"
478−--
471+-- >>> Right c <- Hasql.Connection.acquire (Data.ByteString.Char8.pack connectionString)
472+-- >>> Control.Monad.void $ Hasql.Session.run (Hasql.Session.sql "BEGIN") c
476+-- First, we'll take a look at @SELECT@ statements - usually the bulk of most
477+-- database heavy applications.
479+-- In Rel8, @SELECT@ statements are built using the 'Query' monad. You can
480+-- think of this monad like the ordinary @[]@ (List) monad - but this isn't
481+-- required knowledge.
483+-- To start, we'll look at one of the simplest queries possible - a basic
484+-- @SELECT * FROM@ statement. To select all rows from a table, we use 'each',
485+-- and supply a 'TableSchema'. So to select all @project@ rows, we can write:
487+-- >>> :t each projectSchema
488+-- each projectSchema :: Query (Project Expr)
490+-- Notice that @each@ gives us a @Query@ that yields @Project Expr@ rows. To
491+-- see what this means, let's have a look at a single field of a
494+-- >>> let aProjectExpr = undefined :: Project Expr
495+-- >>> :t projectAuthorId aProjectExpr
496+-- projectAuthorId aProjectExpr :: Expr AuthorId
498+-- Recall we defined @projectAuthorId@ as @Column f AuthorId@. Now we have @f@
499+-- is @Expr@, and @Column Expr AuthorId@ reduces to @Expr AuthorId@. We'll see
500+-- more about @Expr@ soon, but you can think of @Expr a@ as "SQL expressions of
503+-- To execute this @Query@, we pass it to 'select':
505+-- >>> :t select c (each projectSchema)
506+-- select c (each projectSchema) :: MonadIO m => m [Project Identity]
508+-- When we @select@ things containing @Expr@s, Rel8 builds a new response table
509+-- with the @Identity@ interpretation. This means you'll get back plain Haskell
510+-- values. Studying @projectAuthorId@ again, we have:
512+-- >>> let aProjectIdentity = undefined :: Project Identity
513+-- >>> :t projectAuthorId aProjectIdentity
514+-- projectAuthorId aProjectIdentity :: AuthorId
516+-- Here @Column Identity AuthorId@ reduces to just @AuthorId@, with no
517+-- wrappping type at all.
519+-- Putting this all together, we can run our first query:
521+-- >>> select c (each projectSchema) >>= mapM_ print
522+-- Project {projectAuthorId = 1, projectName = "rel8"}
523+-- Project {projectAuthorId = 2, projectName = "aeson"}
524+-- Project {projectAuthorId = 2, projectName = "text"}
528+-- We now know that 'each' is the equivalent of a @SELECT *@ query, but
529+-- sometimes we're only interested in a subset of the columns of a table. To
530+-- restrict the returned columns, we can specify a projection by using 'Query's
531+-- @Functor@ instance:
480−-- thingSchema :: TableSchema (ADT Thing Name)
533+-- >>> select c $ projectName <$> each projectSchema
481−-- thingSchema =
534+-- ["rel8","aeson","text"]
483−-- { schema = Nothing,
536+-- $guideJoins
484−-- name = \"thing\",
537+--
485−-- columns =
538+-- Another common operation in relational databases is to take the @JOIN@ of
486−-- nameADT @Thing
539+-- multiple tables. Rel8 doesn't have a specific join operation, but we can
487−-- \"tag\"
540+-- recover the functionality of a join by selecting all rows of two tables, and
488−-- Employer
541+-- 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',
543+-- 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
544+-- We can do this by simply calling 'each' twice, and then returning a tuple of
504−-- constructor. Similar to 'maybe'.
545+-- their results.
507−-- query :: Query (ADT Thing Expr)
548+-- mapM_ print =<< select c do
508−-- query = do
549+-- author <- each authorSchema
509−-- thingExpr <- each thingSchema
550+-- project <- each projectSchema
510−-- where_ $
551+-- return (projectName project, authorName author)
511−-- deconstructADT @Thing
552+-- :}
512−-- (\employer -> employerName employer ==. lit \"Mary\")
553+-- ("rel8","Ollie")
513−-- (\potato -> grower potato ==. lit \"Mary\")
554+-- ("rel8","Bryan O'Sullivan")
514−-- (lit False) -- Nullary case
555+-- ("rel8","Emily Pillmore")
515−-- thingExpr
556+-- ("aeson","Ollie")
516−-- pure thingExpr
557+-- ("aeson","Bryan O'Sullivan")
517−-- @
558+-- ("aeson","Emily Pillmore")
518−--
559+-- ("text","Ollie")
519−-- SQL output:
560+-- ("text","Bryan O'Sullivan")
520−--
561+-- ("text","Emily Pillmore")
522−-- SELECT
563+-- This isn't quite right, though, as we have ended up pairing up the wrong
523−-- CAST("tag0_1" AS text) as "tag",
564+-- projects and authors. To fix this, we can use 'where_' to restrict the
524−-- CAST("id1_1" AS int4) as "ThingEmployer/_1/employerId",
565+-- 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
567+-- > select c $ do
531−-- "tag" as "tag0_1",
568+-- > author <- each authorSchema
532−-- "id" as "id1_1",
569+-- > project <- each projectSchema
533−-- "name" as "name2_1",
570+-- > where_ $ projectAuthorId project ==. authorId author
534−-- "size" as "size3_1",
571+-- > return (project, author)
535−-- "Mary" as "Mary4_1"
572+--
536−-- FROM "thing" as "T1") as "T1"
573+-- 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))
574+-- 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"
575+-- for the particular joins in your database. In our case, this would be:
541−-- $construction
578+-- projectsForAuthor :: Author Expr -> Query (Project Expr)
579+-- projectsForAuthor a = each projectSchema >>= filter \p ->
580+-- projectAuthorId p ==. authorId a
543−-- To construct an ADT, you can use 'buildADT' or 'constructADT'. Consider the following type:
583+-- Our final query is then:
546−-- data Task f = Pending | Complete (CompletedTask f)
586+-- mapM_ print =<< select c do
587+-- author <- each authorSchema
588+-- project <- projectsForAuthor author
589+-- return (projectName project, authorName author)
592+-- ("aeson","Bryan O'Sullivan")
593+-- ("text","Bryan O'Sullivan")
549−-- 'buildADT' is for constructing values of 'Task' in the 'Expr'
597+-- 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
598+-- 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
599+-- 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
600+--
553−-- want to use. So that means we have the following possible
601+-- In our test database, we can see that there's another author who we haven't
554−-- instantiations of 'buildADT' for 'Task':
602+-- seen yet:
604+-- >>> select c $ authorName <$> each authorSchema
605+-- ["Ollie","Bryan O'Sullivan","Emily Pillmore"]
607+-- Emily wasn't returned in our earlier query because - in our database - she
608+-- doesn't have any registered projects. We can account for this partiality in
609+-- our original query by wrapping the @projectsForAuthor@ call with 'optional':
612+-- mapM_ print =<< select c do
613+-- author <- each authorSchema
614+-- mproject <- optional $ projectsForAuthor author
615+-- return (authorName author, projectName <$> mproject)
556−-- @
617+-- ("Ollie",Just "rel8")
557−-- > :t buildADT @Task @\"Pending\"
618+-- ("Bryan O'Sullivan",Just "aeson")
558−-- buildADT @Task @\"Pending\" :: ADT Task Expr
619+-- ("Bryan O'Sullivan",Just "text")
559−-- > :t buildADT @Task @\"Complete\"
620+-- ("Emily Pillmore",Nothing)
560−-- buildADT @Task @\"Complete\" :: CompletedTask Expr -> ADT Task Expr
621+
561−-- @
622+-- $guideAggregation
563−-- Note that as the "Pending" constructor has no fields, @buildADT
624+-- Aggregations are operations like @sum@ and @count@ - operations that reduce
564−-- \@Task \@"Pending"@ is equivalent to @lit Pending@. But @buildADT
625+-- multiple rows to single values. To perform aggregations in Rel8, we can use
565−-- \@Task \@"Complete"@ is not the same as @lit . Complete@:
626+-- the 'aggregate' function, which takes a 'Query' of aggregated expressions,
627+-- runs the aggregation, and returns aggregated rows.
567−-- @
629+-- To start, let's look at a simple aggregation that tells us how many projects
568−-- > :t lit . Complete
630+-- exist:
569−-- lit . Complete :: CompletedTask Result -> ADT Task Expr
571−--
632+-- >>> error "TODO"
573−-- Note that the former takes a @CompletedTask Expr@ while the latter
634+-- Rel8 is also capable of aggregating multiple rows into a single row by
574−-- takes a @CompletedTask Result@. The former is more powerful because
635+-- 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
636+-- of the row-orientated nature of SQL and write queries that return tree-like
576−-- query.
637+-- structures. Earlier we saw an example of returning authors with their
577−--
638+-- 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:
639+-- relationship between authors and their projects.
641+-- Let's look again at a query that returns authors and their projects, and
642+-- focus on the /type/ of that query.
645+-- projectsForAuthor a = each projectSchema >>= filter \p ->
646+-- projectAuthorId p ==. authorId a
582−-- showQuery $ values
650+-- let authorsAndProjects = do
583−-- [ buildADT @Task @\"Pending\"
651+-- author <- each authorSchema
584−-- , buildADT @Task @\"Complete\" CompletedTask {date = Rel8.Expr.Time.now}
652+-- project <- projectsForAuthor author
585−-- ]
653+-- return (author, project)
657+-- >>> :t select c authorsAndProjects
658+-- select c authorsAndProjects
659+-- :: MonadIO m => m [(Author Identity, Project Identity)]
661+-- Our query gives us a single list of pairs of authors and projects. However,
662+-- with our domain knowledge of the schema, this isn't a great type - what we'd
663+-- rather have is a list of pairs of authors and /lists/ of projects. That is,
664+-- what we'd like is:
666+-- > [(Author Identity, [Project Identity])]
668+-- This would be a much better type! Rel8 can produce a query with this type by
669+-- simply wrapping the call to @projectsForAuthor@ with either 'some' or
670+-- 'many'. Here we'll use 'many', which allows for the possibility of an author
671+-- to have no projects:
674+-- mapM_ print =<< select c do
675+-- author <- each authorSchema
676+-- projectNames <- many $ projectName <$> projectsForAuthor author
677+-- return (authorName author, projectNames)
587−-- @
679+-- ("Ollie",["rel8"])
588−--
680+-- ("Bryan O'Sullivan",["aeson","text"])
589−-- This produces the following SQL:
681+-- ("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.