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