@@ -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
@@ -26,44 +34,33 @@ module Rel8
26 , DBSemigroup(..)
34 , DBSemigroup(..)
27 , DBMonoid(..)
35 , DBMonoid(..)
30 , DBFractional
37 , DBFractional
33 -- * Tables and higher-kinded tables
39 -- * Tables and higher-kinded tables
34− , Rel8able, KRel8able
40+ , Rel8able
36− , HADT
42+ , Default, Label
40 , HNonEmpty
45 , HNonEmpty
45− , Table(..)
48+ , Table
48 , AltTable((<|>:))
49 , AltTable((<|>:))
49− , AlternativeTable( emptyTable )
50− , EqTable(..), (==:), (/=:)
50+ , EqTable, (==:), (/=:)
51− , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
51+ , OrdTable, ascTable, descTable
57 -- ** @MaybeTable@
56 -- ** @MaybeTable@
58 , MaybeTable
57 , MaybeTable
59 , maybeTable, ($?), nothingTable, justTable
58 , maybeTable, ($?), nothingTable, justTable
60 , isNothingTable, isJustTable
59 , isNothingTable, isJustTable
62 , optional
60 , optional
63 , catMaybeTable
61 , catMaybeTable
64 , traverseMaybeTable
63 , traverseMaybeTable
65− , aggregateMaybeTable
68 -- ** @EitherTable@
65 -- ** @EitherTable@
69 , EitherTable
66 , EitherTable
@@ -71,9 +68,8 @@ module Rel8
71 , isLeftTable, isRightTable
68 , isLeftTable, isRightTable
72 , keepLeftTable
69 , keepLeftTable
73 , keepRightTable
70 , keepRightTable
74 , bitraverseEitherTable
72 , bitraverseEitherTable
75− , aggregateEitherTable
78 -- ** @TheseTable@
74 -- ** @TheseTable@
79 , TheseTable
75 , TheseTable
@@ -81,73 +77,22 @@ module Rel8
81 , isThisTable, isThatTable, isThoseTable
77 , isThisTable, isThatTable, isThoseTable
82 , hasHereTable, hasThereTable
78 , hasHereTable, hasThereTable
83 , justHereTable, justThereTable
79 , justHereTable, justThereTable
86 , keepHereTable, loseHereTable
81 , keepHereTable, loseHereTable
87 , keepThereTable, loseThereTable
82 , keepThereTable, loseThereTable
88 , keepThisTable, loseThisTable
83 , keepThisTable, loseThisTable
89 , keepThatTable, loseThatTable
84 , keepThatTable, loseThatTable
90 , keepThoseTable, loseThoseTable
85 , keepThoseTable, loseThoseTable
91 , bitraverseTheseTable
87 , bitraverseTheseTable
92− , aggregateTheseTable
95 -- ** @ListTable@
89 -- ** @ListTable@
96 , ListTable
90 , ListTable
104 -- ** @NonEmptyTable@
93 -- ** @NonEmptyTable@
105 , NonEmptyTable
94 , 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
97 -- ** Table schemas
153 , TableSchema(..)
98 , TableSchema(..)
@@ -160,20 +105,16 @@ module Rel8
161 , litExpr
106 , litExpr
162 , unsafeCastExpr
107 , unsafeCastExpr
165 -- ** @null@
109 -- ** @null@
170 , nullify
111 , nullify
171 , nullable
112 , nullable
173 , isNonNull
114 , isNonNull
174− , mapNull
115+ , mapNullable
175− , liftOpNull
116+ , liftOpNullable
176− , catNull
117+ , catNullable
177 , coalesce
118 , coalesce
179 -- ** Boolean operations
120 -- ** Boolean operations
@@ -184,7 +125,6 @@ module Rel8
184 , (==.), (/=.), (==?), (/=?)
125 , (==.), (/=.), (==?), (/=?)
186 , boolExpr, caseExpr
127 , boolExpr, caseExpr
189 -- ** Ordering
129 -- ** Ordering
@@ -202,13 +142,6 @@ module Rel8
203 , showQuery
143 , showQuery
207− , Projectable( project )
208− , Biprojectable( biproject )
212 -- ** Selecting rows
145 -- ** Selecting rows
213 , Selects
146 , Selects
@@ -217,8 +150,8 @@ module Rel8
217 -- ** Filtering
150 -- ** Filtering
220− , present
153+ , whereExists
221− , absent
154+ , whereNotExists
222 , distinct
155 , distinct
223 , distinctOn
156 , distinctOn
224 , distinctOnBy
157 , distinctOnBy
@@ -248,16 +181,13 @@ module Rel8
249 -- ** Aggregation
182 -- ** Aggregation
250 , Aggregate
183 , Aggregate
252 , aggregate
184 , aggregate
255 , listAgg, listAggExpr
185 , listAgg, listAggExpr
257 , nonEmptyAgg, nonEmptyAggExpr
186 , nonEmptyAgg, nonEmptyAggExpr
258 , DBMax, max
188 , DBMax, max
259 , DBMin, min
189 , DBMin, min
260− , DBSum, sum, sumWhere, avg
190+ , DBSum, sum, sumWhere
261 , DBString, stringAgg
191 , DBString, stringAgg
263 , countStar
193 , countStar
@@ -266,6 +196,11 @@ module Rel8
201+ , distributeAggregate
269 -- ** Ordering
204 -- ** Ordering
270 , orderBy
205 , orderBy
@@ -274,71 +209,40 @@ module Rel8
274 , nullsFirst
209 , nullsFirst
275 , nullsLast
210 , nullsLast
277− -- ** Window functions
304 , Serializable
213 , Serializable
308 -- * Running statements
215 -- * Running statements
311 -- ** @SELECT@
216 -- ** @SELECT@
314 -- ** @INSERT@
219 -- ** @INSERT@
315 , Insert(..)
220 , Insert(..)
316 , OnConflict(..)
221 , OnConflict(..)
319− , unsafeDefault
223+ , toInsert
320− , showInsert
224+ , toInsertDefaults
322 -- ** @DELETE@
226 -- ** @DELETE@
323 , Delete(..)
227 , Delete(..)
327 -- ** @UPDATE@
230 -- ** @UPDATE@
330− , showUpdate
232+ , Update(..)
332 -- ** @.. RETURNING@
234 -- ** @.. RETURNING@
333 , Returning(..)
235 , Returning(..)
335− -- ** @CREATE VIEW@
237+ -- * TODO
336− , createView
238+ -- TODO
337− , createOrReplaceView
239+ -- These need organizing, but are reachable from Rel8's documentation so we
338−
240+ -- do need to export and document them.
339− -- ** Sequences
241+ , HasNullability
340− , nextval
242+ , NotNull
341− , evaluate
243+ , HTable
@@ -346,94 +250,64 @@ import Prelude ()
348import Rel8.Aggregate
252import 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
253import Rel8.Expr
359import Rel8.Expr.Aggregate
254import Rel8.Expr.Aggregate
360import Rel8.Expr.Bool
255import Rel8.Expr.Bool
361−import Rel8.Expr.Default
362import Rel8.Expr.Eq
256import Rel8.Expr.Eq
363import Rel8.Expr.Function
257import Rel8.Expr.Function
364import Rel8.Expr.Null
258import Rel8.Expr.Null
365−import Rel8.Expr.Opaleye (unsafeCastExpr, unsafeLiteral)
366import Rel8.Expr.Ord
259import Rel8.Expr.Ord
367import Rel8.Expr.Order
260import Rel8.Expr.Order
368import Rel8.Expr.Serialize
261import 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
262import Rel8.Order
374import Rel8.Query
263import Rel8.Query
375import Rel8.Query.Aggregate
264import Rel8.Query.Aggregate
376import Rel8.Query.Distinct
265import Rel8.Query.Distinct
377import Rel8.Query.Each
266import Rel8.Query.Each
378import Rel8.Query.Either
267import Rel8.Query.Either
379−import Rel8.Query.Evaluate
380import Rel8.Query.Exists
268import Rel8.Query.Exists
381import Rel8.Query.Filter
269import Rel8.Query.Filter
382−import Rel8.Query.Indexed
383import Rel8.Query.Limit
270import Rel8.Query.Limit
384import Rel8.Query.List
271import Rel8.Query.List
385import Rel8.Query.Maybe
272import Rel8.Query.Maybe
386import Rel8.Query.Null
273import Rel8.Query.Null
387import Rel8.Query.Order
274import Rel8.Query.Order
388−import Rel8.Query.Rebind
389import Rel8.Query.SQL (showQuery)
275import Rel8.Query.SQL (showQuery)
390import Rel8.Query.Set
276import Rel8.Query.Set
391import Rel8.Query.These
277import Rel8.Query.These
392import Rel8.Query.Values
278import Rel8.Query.Values
393−import Rel8.Query.Window
279+import Rel8.Schema.Column
280+import Rel8.Schema.Context.Label
394−import Rel8.Schema.Field
281+import Rel8.Schema.Generic
395import Rel8.Schema.HTable
282import Rel8.Schema.HTable
396import Rel8.Schema.Name
283import Rel8.Schema.Name
397−import Rel8.Schema.Null hiding ( nullable )
284+import Rel8.Schema.Nullability
398−import Rel8.Schema.Result ( Result )
399import Rel8.Schema.Table
285import Rel8.Schema.Table
400import Rel8.Statement.Delete
286import Rel8.Statement.Delete
401import Rel8.Statement.Insert
287import Rel8.Statement.Insert
402−import Rel8.Statement.OnConflict
403import Rel8.Statement.Returning
288import Rel8.Statement.Returning
404import Rel8.Statement.Select
289import Rel8.Statement.Select
405−import Rel8.Statement.SQL
406import Rel8.Statement.Update
290import Rel8.Statement.Update
407−import Rel8.Statement.View
408import Rel8.Table
291import Rel8.Table
409−import Rel8.Table.ADT
410import Rel8.Table.Aggregate
292import Rel8.Table.Aggregate
411import Rel8.Table.Alternative
293import Rel8.Table.Alternative
412import Rel8.Table.Bool
294import Rel8.Table.Bool
413import Rel8.Table.Either
295import Rel8.Table.Either
414import Rel8.Table.Eq
296import Rel8.Table.Eq
415−import Rel8.Table.HKD
297+import Rel8.Table.Insert
416import Rel8.Table.List
298import Rel8.Table.List
417import Rel8.Table.Maybe
299import Rel8.Table.Maybe
418import Rel8.Table.Name
300import Rel8.Table.Name
419import Rel8.Table.NonEmpty
301import Rel8.Table.NonEmpty
420−import Rel8.Table.Null
421−import Rel8.Table.Opaleye ( castTable )
422import Rel8.Table.Ord
302import Rel8.Table.Ord
423import Rel8.Table.Order
303import Rel8.Table.Order
424−import Rel8.Table.Projection
425−import Rel8.Table.Rel8able ()
426import Rel8.Table.Serialize
304import Rel8.Table.Serialize
427import Rel8.Table.These
305import Rel8.Table.These
428−import Rel8.Table.Transpose
429−import Rel8.Table.Window
430import Rel8.Type
306import Rel8.Type
431−import Rel8.Type.Composite
432import Rel8.Type.Eq
307import Rel8.Type.Eq
433−import Rel8.Type.Enum
434import Rel8.Type.Information
308import Rel8.Type.Information
435−import Rel8.Type.JSONBEncoded
436import Rel8.Type.JSONEncoded
309import Rel8.Type.JSONEncoded
310+import Rel8.Type.JSONBEncoded
437import Rel8.Type.Monoid
311import Rel8.Type.Monoid
438import Rel8.Type.Num
312import Rel8.Type.Num
439import Rel8.Type.Ord
313import Rel8.Type.Ord
@@ -441,216 +315,362 @@ import Rel8.Type.ReadShow
441import Rel8.Type.Semigroup
315import Rel8.Type.Semigroup
442import Rel8.Type.String
316import Rel8.Type.String
443import Rel8.Type.Sum
317import Rel8.Type.Sum
444−import Rel8.Window
318+import Rel8.Expr.Opaleye (unsafeCastExpr)
447−-- $running
321+-- $setup
448−-- To run queries and otherwise interact with a PostgreSQL database, Rel8
322+--
449−-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
323+-- 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
324+-- database for Haskell packages. We'll take a look at idiomatic usage of Rel8,
451−-- `DuplicateRecordFields` language extension enabled.
325+-- mapping tables to Haskell, and then look at writing some simple queries.
453−-- $adts
327+-- The documentation in this library uses the following language extensions:
454−-- Algebraic data types can be modelled between Haskell and SQL.
328+--
329+-- >>> :set -XBlockArguments
330+-- >>> :set -XDeriveAnyClass
331+-- >>> :set -XDeriveGeneric
332+-- >>> :set -XDerivingStrategies
333+-- >>> :set -XDerivingVia
334+-- >>> :set -XDuplicateRecordFields
335+-- >>> :set -XGeneralizedNewtypeDeriving
336+-- >>> :set -XOverloadedStrings
337+-- >>> :set -XStandaloneDeriving
338+-- >>> :set -XTypeApplications
339+-- >>> :set -XTypeFamilies
341+-- Before we start writing any Haskell, let's take a look at the schema we'll
344+-- > author: project:
346+-- > Column │ Type │ Nullable Column │ Type │ Nullable
347+-- > ═══════════╪═════════╪══════════ ═══════════╪═════════╪══════════
348+-- > author_id │ integer │ not null author_id │ integer │ not null
349+-- > name │ text │ not null name │ text │ not null
352+-- Our schema consists of two tables - @author@ and @project@. A @project@
353+-- always has an @author@, but not all @author@s have projects. Each @author@
354+-- has a name and (maybe) an associated website, and each project has a name.
356+-- Now that we've seen our schema, we can begin writing a mapping in Rel8. The
357+-- idiomatic way to map a table is to use a record that is parameterised what
358+-- Rel8 calls an /interpretation functor/, and to define each field with
359+-- 'Column'. For this type to be usable with Rel8 we need it to be an instance
360+-- of 'HigherKindedTable', which can be derived with a combination of
361+-- @DeriveAnyClass@ and @DeriveGeneric@.
363+-- Following these steps for @author@, we have:
365+-- > data Author f = Author
366+-- > { authorId :: Column f Int64
367+-- > , name :: Column f Text
368+-- > , url :: Column f (Maybe Text)
369+-- > } deriving (Generic, HigherKindedTable)
371+-- This is a perfectly reasonable definition, but cautious readers might notice
372+-- a problem - in particular, with the type of the @authorId@ field. While
373+-- @Int64@ is correct, it's not the best type. If we had other identifier types
374+-- in our project, it would be too easy to accidentally mix them up and create
375+-- nonsensical joins. As Haskell programmers, we often solve this problem by
376+-- creating @newtype@ wrappers, and we can also use this technique with Rel8:
379+-- newtype AuthorId = AuthorId { toInt64 :: Int64 }
380+-- deriving newtype (DBEq, DBType, Eq, Show)
456−-- * Your SQL table needs a certain text field that tags which Haskell constructor is in use.
383+-- 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:
385+-- >>> :{
386+-- data Author f = Author
387+-- { authorId :: Column f AuthorId
388+-- , authorName :: Column f Text
389+-- , authorUrl :: Column f (Maybe Text)
391+-- deriving stock Generic
392+-- deriving anyclass HigherKindedTable
462−-- @
395+-- 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}
397+-- >>> :{
398+-- data Project f = Project
399+-- { projectAuthorId :: Column f AuthorId
400+-- , projectName :: Column f Text
467-- deriving stock Generic
402-- deriving stock Generic
468−-- deriving anyclass Rel8able
403+-- deriving anyclass HigherKindedTable
470−-- data Potato f = Potato { size :: f Int32, grower :: f Text }
406+-- To show query results in this documentation, we'll also need @Show@
471−-- deriving stock Generic
407+-- instances: Unfortunately these definitions look a bit scary, but they are
472−-- deriving anyclass Rel8able
408+-- essentially just @deriving (Show)@:
474−
410+-- >>> deriving stock instance f ~ Identity => Show (Author f)
475−-- $naming
411+-- >>> deriving stock instance f ~ Identity => Show (Project f)
413+-- These data types describe the structural mapping of the tables, but we also
414+-- need to specify a 'TableSchema' for each table. A @TableSchema@ contains the
415+-- name of the table and the name of all columns in the table, which will
416+-- ultimately allow us to @SELECT@ and @INSERT@ rows for these tables.
418+-- To define a @TableSchema@, we just need to fill construct appropriate
419+-- @TableSchema@ values. When it comes to the @tableColumns@ field, we
420+-- construct values of our data types above, and set each field to the name of
421+-- the column that it maps to:
423+-- First, @authorSchema@ describes the column names of the @author@ table when
424+-- associated with the @Author@ type:
427+-- authorSchema :: TableSchema (Author ColumnSchema)
428+-- authorSchema = TableSchema
429+-- { tableName = "author"
430+-- , tableSchema = Nothing
431+-- , tableColumns = Author
432+-- { authorId = "author_id"
433+-- , authorName = "name"
434+-- , authorUrl = "url"
439+-- And likewise for @project@ and @Project@:
442+-- projectSchema :: TableSchema (Project ColumnSchema)
443+-- projectSchema = TableSchema
444+-- { tableName = "project"
445+-- , tableSchema = Nothing
446+-- , tableColumns = Project
447+-- { projectAuthorId = "author_id"
448+-- , projectName = "name"
453+-- Aside: you might be wondering why this information isn't in the definitions
454+-- of @Author@ and @Project@ above. Rel8 decouples @TableSchema@ from the data
455+-- types themselves, as not all tables you define will necessarily have a
456+-- schema. For example, Rel8 allows you to define helper types to simplify the
457+-- types of queries - these tables only exist at query time, but there is no
458+-- corresponding base table. We'll see more on this idea later!
460+-- With these table definitions, we can now start writing some queries! To
461+-- actually run a query, we'll need a database connection. Rel8 uses the
462+-- @hasql@ library, and for this documentation we'll get the connection string
463+-- from the @$TEST_DATABASE_URL@ environment variable.
477−-- First, in your 'TableSchema', name your type like this:
465+-- >>> connectionString <- System.Environment.getEnv "TEST_DATABASE_URL"
478−--
466+-- >>> Right c <- Hasql.Connection.acquire (Data.ByteString.Char8.pack connectionString)
467+-- >>> Control.Monad.void $ Hasql.Session.run (Hasql.Session.sql "BEGIN") c
471+-- First, we'll take a look at @SELECT@ statements - usually the bulk of most
472+-- database heavy applications.
474+-- In Rel8, @SELECT@ statements are built using the 'Query' monad. You can
475+-- think of this monad like the ordinary @[]@ (List) monad - but this isn't
476+-- required knowledge.
478+-- To start, we'll look at one of the simplest queries possible - a basic
479+-- @SELECT * FROM@ statement. To select all rows from a table, we use 'each',
480+-- and supply a 'TableSchema'. So to select all @project@ rows, we can write:
482+-- >>> :t each projectSchema
483+-- each projectSchema :: Query (Project Expr)
485+-- Notice that @each@ gives us a @Query@ that yields @Project Expr@ rows. To
486+-- see what this means, let's have a look at a single field of a
489+-- >>> let aProjectExpr = undefined :: Project Expr
490+-- >>> :t projectAuthorId aProjectExpr
491+-- projectAuthorId aProjectExpr :: Expr AuthorId
493+-- Recall we defined @projectAuthorId@ as @Column f AuthorId@. Now we have @f@
494+-- is @Expr@, and @Column Expr AuthorId@ reduces to @Expr AuthorId@. We'll see
495+-- more about @Expr@ soon, but you can think of @Expr a@ as "SQL expressions of
498+-- To execute this @Query@, we pass it to 'select':
500+-- >>> :t select c (each projectSchema)
501+-- select c (each projectSchema) :: MonadIO m => m [Project Identity]
503+-- When we @select@ things containing @Expr@s, Rel8 builds a new response table
504+-- with the @Identity@ interpretation. This means you'll get back plain Haskell
505+-- values. Studying @projectAuthorId@ again, we have:
507+-- >>> let aProjectIdentity = undefined :: Project Identity
508+-- >>> :t projectAuthorId aProjectIdentity
509+-- projectAuthorId aProjectIdentity :: AuthorId
511+-- Here @Column Identity AuthorId@ reduces to just @AuthorId@, with no
512+-- wrappping type at all.
514+-- Putting this all together, we can run our first query:
516+-- >>> select c (each projectSchema) >>= mapM_ print
517+-- Project {projectAuthorId = 1, projectName = "rel8"}
518+-- Project {projectAuthorId = 2, projectName = "aeson"}
519+-- Project {projectAuthorId = 2, projectName = "text"}
523+-- We now know that 'each' is the equivalent of a @SELECT *@ query, but
524+-- sometimes we're only interested in a subset of the columns of a table. To
525+-- restrict the returned columns, we can specify a projection by using 'Query's
526+-- @Functor@ instance:
480−-- thingSchema :: TableSchema (ADT Thing Name)
528+-- >>> select c $ projectName <$> each projectSchema
481−-- thingSchema =
529+-- ["rel8","aeson","text"]
483−-- { schema = Nothing,
531+-- $guideJoins
484−-- name = \"thing\",
532+--
485−-- columns =
533+-- Another common operation in relational databases is to take the @JOIN@ of
486−-- nameADT @Thing
534+-- multiple tables. Rel8 doesn't have a specific join operation, but we can
487−-- \"tag\"
535+-- recover the functionality of a join by selecting all rows of two tables, and
488−-- Employer
536+-- 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',
538+-- 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
539+-- We can do this by simply calling 'each' twice, and then returning a tuple of
504−-- constructor. Similar to 'maybe'.
540+-- their results.
507−-- query :: Query (ADT Thing Expr)
543+-- mapM_ print =<< select c do
508−-- query = do
544+-- author <- each authorSchema
509−-- thingExpr <- each thingSchema
545+-- project <- each projectSchema
510−-- where_ $
546+-- return (projectName project, authorName author)
511−-- deconstructADT @Thing
547+-- :}
512−-- (\employer -> employerName employer ==. lit \"Mary\")
548+-- ("rel8","Ollie")
513−-- (\potato -> grower potato ==. lit \"Mary\")
549+-- ("rel8","Bryan O'Sullivan")
514−-- (lit False) -- Nullary case
550+-- ("rel8","Emily Pillmore")
515−-- thingExpr
551+-- ("aeson","Ollie")
516−-- pure thingExpr
552+-- ("aeson","Bryan O'Sullivan")
517−-- @
553+-- ("aeson","Emily Pillmore")
518−--
554+-- ("text","Ollie")
519−-- SQL output:
555+-- ("text","Bryan O'Sullivan")
520−--
556+-- ("text","Emily Pillmore")
522−-- SELECT
558+-- This isn't quite right, though, as we have ended up pairing up the wrong
523−-- CAST("tag0_1" AS text) as "tag",
559+-- projects and authors. To fix this, we can use 'where_' to restrict the
524−-- CAST("id1_1" AS int4) as "ThingEmployer/_1/employerId",
560+-- 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
562+-- > select c $ do
531−-- "tag" as "tag0_1",
563+-- > author <- each authorSchema
532−-- "id" as "id1_1",
564+-- > project <- each projectSchema
533−-- "name" as "name2_1",
565+-- > where_ $ projectAuthorId project ==. authorId author
534−-- "size" as "size3_1",
566+-- > return (project, author)
535−-- "Mary" as "Mary4_1"
567+--
536−-- FROM "thing" as "T1") as "T1"
568+-- 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))
569+-- 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"
570+-- for the particular joins in your database. In our case, this would be:
541−-- $construction
573+-- projectsForAuthor :: Author Expr -> Query (Project Expr)
574+-- projectsForAuthor a = each projectSchema >>= filter \p ->
575+-- projectAuthorId p ==. authorId a
543−-- To construct an ADT, you can use 'buildADT' or 'constructADT'. Consider the following type:
578+-- Our final query is then:
546−-- data Task f = Pending | Complete (CompletedTask f)
581+-- mapM_ print =<< select c do
582+-- author <- each authorSchema
583+-- project <- projectsForAuthor author
584+-- return (projectName project, authorName author)
587+-- ("aeson","Bryan O'Sullivan")
588+-- ("text","Bryan O'Sullivan")
549−-- 'buildADT' is for constructing values of 'Task' in the 'Expr'
592+-- 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
593+-- 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
594+-- 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
595+--
553−-- want to use. So that means we have the following possible
596+-- In our test database, we can see that there's another author who we haven't
554−-- instantiations of 'buildADT' for 'Task':
597+-- seen yet:
599+-- >>> select c $ authorName <$> each authorSchema
600+-- ["Ollie","Bryan O'Sullivan","Emily Pillmore"]
602+-- Emily wasn't returned in our earlier query because - in our database - she
603+-- doesn't have any registered projects. We can account for this partiality in
604+-- our original query by wrapping the @projectsForAuthor@ call with 'optional':
607+-- mapM_ print =<< select c do
608+-- author <- each authorSchema
609+-- mproject <- optional $ projectsForAuthor author
610+-- return (authorName author, projectName <$> mproject)
556−-- @
612+-- ("Ollie",Just "rel8")
557−-- > :t buildADT @Task @\"Pending\"
613+-- ("Bryan O'Sullivan",Just "aeson")
558−-- buildADT @Task @\"Pending\" :: ADT Task Expr
614+-- ("Bryan O'Sullivan",Just "text")
559−-- > :t buildADT @Task @\"Complete\"
615+-- ("Emily Pillmore",Nothing)
560−-- buildADT @Task @\"Complete\" :: CompletedTask Expr -> ADT Task Expr
616+
561−-- @
617+-- $guideAggregation
563−-- Note that as the "Pending" constructor has no fields, @buildADT
619+-- Aggregations are operations like @sum@ and @count@ - operations that reduce
564−-- \@Task \@"Pending"@ is equivalent to @lit Pending@. But @buildADT
620+-- multiple rows to single values. To perform aggregations in Rel8, we can use
565−-- \@Task \@"Complete"@ is not the same as @lit . Complete@:
621+-- the 'aggregate' function, which takes a 'Query' of aggregated expressions,
622+-- runs the aggregation, and returns aggregated rows.
567−-- @
624+-- To start, let's look at a simple aggregation that tells us how many projects
568−-- > :t lit . Complete
625+-- exist:
569−-- lit . Complete :: CompletedTask Result -> ADT Task Expr
571−--
627+-- >>> error "TODO"
573−-- Note that the former takes a @CompletedTask Expr@ while the latter
629+-- Rel8 is also capable of aggregating multiple rows into a single row by
574−-- takes a @CompletedTask Result@. The former is more powerful because
630+-- 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
631+-- of the row-orientated nature of SQL and write queries that return tree-like
576−-- query.
632+-- structures. Earlier we saw an example of returning authors with their
577−--
633+-- 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:
634+-- relationship between authors and their projects.
636+-- Let's look again at a query that returns authors and their projects, and
637+-- focus on the /type/ of that query.
640+-- projectsForAuthor a = each projectSchema >>= filter \p ->
641+-- projectAuthorId p ==. authorId a
582−-- showQuery $ values
645+-- let authorsAndProjects = do
583−-- [ buildADT @Task @\"Pending\"
646+-- author <- each authorSchema
584−-- , buildADT @Task @\"Complete\" CompletedTask {date = Rel8.Expr.Time.now}
647+-- project <- projectsForAuthor author
585−-- ]
648+-- return (author, project)
652+-- >>> :t select c authorsAndProjects
653+-- select c authorsAndProjects
654+-- :: MonadIO m => m [(Author Identity, Project Identity)]
656+-- Our query gives us a single list of pairs of authors and projects. However,
657+-- with our domain knowledge of the schema, this isn't a great type - what we'd
658+-- rather have is a list of pairs of authors and /lists/ of projects. That is,
659+-- what we'd like is:
661+-- > [(Author Identity, [Project Identity])]
663+-- This would be a much better type! Rel8 can produce a query with this type by
664+-- simply wrapping the call to @projectsForAuthor@ with either 'some' or
665+-- 'many'. Here we'll use 'many', which allows for the possibility of an author
666+-- to have no projects:
669+-- mapM_ print =<< select c do
670+-- author <- each authorSchema
671+-- projectNames <- many $ projectName <$> projectsForAuthor author
672+-- return (authorName author, projectNames)
587−-- @
674+-- ("Ollie",["rel8"])
588−--
675+-- ("Bryan O'Sullivan",["aeson","text"])
589−-- This produces the following SQL:
676+-- ("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.