@@ -1,19 +1,7 @@
1{-# language DuplicateRecordFields #-}
1{-# language DuplicateRecordFields #-}
4− ( -- * Getting Started
16− -- * Database types
4+ ( -- * Database types
17 -- ** @DBType@
5 -- ** @DBType@
18 DBType(..)
6 DBType(..)
@@ -25,6 +13,10 @@ module Rel8
25 -- **** @ReadShow@
13 -- **** @ReadShow@
26 , ReadShow(..)
14 , ReadShow(..)
17+ , Composite(..), DBComposite(..), compose, decompose
18+ , Enum(..), DBEnum(..), Enumable
28 -- *** @TypeInformation@
20 -- *** @TypeInformation@
29 , TypeInformation(..)
21 , TypeInformation(..)
30 , mapTypeInformation
22 , mapTypeInformation
@@ -34,33 +26,44 @@ module Rel8
34 , DBSemigroup(..)
26 , DBSemigroup(..)
35 , DBMonoid(..)
27 , DBMonoid(..)
37 , DBFractional
30 , DBFractional
39 -- * Tables and higher-kinded tables
33 -- * Tables and higher-kinded tables
40− , Rel8able
34+ , Rel8able, KRel8able
42− , Default, Label
36+ , HADT
45 , HNonEmpty
40 , HNonEmpty
48− , Table
45+ , Table(..)
49 , AltTable((<|>:))
48 , AltTable((<|>:))
49+ , AlternativeTable( emptyTable )
50− , EqTable, (==:), (/=:)
50+ , EqTable(..), (==:), (/=:)
51− , OrdTable, ascTable, descTable
51+ , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
56 -- ** @MaybeTable@
57 -- ** @MaybeTable@
57 , MaybeTable
58 , MaybeTable
58 , maybeTable, ($?), nothingTable, justTable
59 , maybeTable, ($?), nothingTable, justTable
59 , isNothingTable, isJustTable
60 , isNothingTable, isJustTable
60 , optional
62 , optional
61 , catMaybeTable
63 , catMaybeTable
63 , traverseMaybeTable
64 , traverseMaybeTable
65+ , aggregateMaybeTable
65 -- ** @EitherTable@
68 -- ** @EitherTable@
66 , EitherTable
69 , EitherTable
@@ -68,8 +71,9 @@ module Rel8
68 , isLeftTable, isRightTable
71 , isLeftTable, isRightTable
69 , keepLeftTable
72 , keepLeftTable
70 , keepRightTable
73 , keepRightTable
72 , bitraverseEitherTable
74 , bitraverseEitherTable
75+ , aggregateEitherTable
74 -- ** @TheseTable@
78 -- ** @TheseTable@
75 , TheseTable
79 , TheseTable
@@ -77,22 +81,73 @@ module Rel8
77 , isThisTable, isThatTable, isThoseTable
81 , isThisTable, isThatTable, isThoseTable
78 , hasHereTable, hasThereTable
82 , hasHereTable, hasThereTable
79 , justHereTable, justThereTable
83 , justHereTable, justThereTable
81 , keepHereTable, loseHereTable
86 , keepHereTable, loseHereTable
82 , keepThereTable, loseThereTable
87 , keepThereTable, loseThereTable
83 , keepThisTable, loseThisTable
88 , keepThisTable, loseThisTable
84 , keepThatTable, loseThatTable
89 , keepThatTable, loseThatTable
85 , keepThoseTable, loseThoseTable
90 , keepThoseTable, loseThoseTable
87 , bitraverseTheseTable
91 , bitraverseTheseTable
92+ , aggregateTheseTable
89 -- ** @ListTable@
95 -- ** @ListTable@
90 , ListTable
96 , ListTable
93 -- ** @NonEmptyTable@
104 -- ** @NonEmptyTable@
94 , NonEmptyTable
105 , 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
97 -- ** Table schemas
152 -- ** Table schemas
98 , TableSchema(..)
153 , TableSchema(..)
@@ -105,16 +160,20 @@ module Rel8
106 , litExpr
161 , litExpr
107 , unsafeCastExpr
162 , unsafeCastExpr
109 -- ** @null@
165 -- ** @null@
111 , nullify
170 , nullify
112 , nullable
171 , nullable
114 , isNonNull
173 , isNonNull
115− , mapNullable
174+ , mapNull
116− , liftOpNullable
175+ , liftOpNull
117− , catNullable
176+ , catNull
118 , coalesce
177 , coalesce
120 -- ** Boolean operations
179 -- ** Boolean operations
@@ -125,6 +184,7 @@ module Rel8
125 , (==.), (/=.), (==?), (/=?)
184 , (==.), (/=.), (==?), (/=?)
127 , boolExpr, caseExpr
186 , boolExpr, caseExpr
129 -- ** Ordering
189 -- ** Ordering
@@ -142,6 +202,13 @@ module Rel8
143 , showQuery
203 , showQuery
207+ , Projectable( project )
208+ , Biprojectable( biproject )
145 -- ** Selecting rows
212 -- ** Selecting rows
146 , Selects
213 , Selects
@@ -150,8 +217,8 @@ module Rel8
150 -- ** Filtering
217 -- ** Filtering
153− , whereExists
220+ , present
154− , whereNotExists
221+ , absent
155 , distinct
222 , distinct
156 , distinctOn
223 , distinctOn
157 , distinctOnBy
224 , distinctOnBy
@@ -181,13 +248,16 @@ module Rel8
182 -- ** Aggregation
249 -- ** Aggregation
183 , Aggregate
250 , Aggregate
184 , aggregate
252 , aggregate
185 , listAgg, listAggExpr
255 , listAgg, listAggExpr
186 , nonEmptyAgg, nonEmptyAggExpr
257 , nonEmptyAgg, nonEmptyAggExpr
188 , DBMax, max
258 , DBMax, max
189 , DBMin, min
259 , DBMin, min
190− , DBSum, sum, sumWhere
260+ , DBSum, sum, sumWhere, avg
191 , DBString, stringAgg
261 , DBString, stringAgg
193 , countStar
263 , countStar
@@ -196,11 +266,6 @@ module Rel8
201− , distributeAggregate
204 -- ** Ordering
269 -- ** Ordering
205 , orderBy
270 , orderBy
@@ -209,40 +274,71 @@ module Rel8
209 , nullsFirst
274 , nullsFirst
210 , nullsLast
275 , nullsLast
277+ -- ** Window functions
213 , Serializable
304 , Serializable
215 -- * Running statements
308 -- * Running statements
216 -- ** @SELECT@
311 -- ** @SELECT@
219 -- ** @INSERT@
314 -- ** @INSERT@
220 , Insert(..)
315 , Insert(..)
221 , OnConflict(..)
316 , OnConflict(..)
223− , toInsert
319+ , unsafeDefault
224− , toInsertDefaults
320+ , showInsert
226 -- ** @DELETE@
322 -- ** @DELETE@
227 , Delete(..)
323 , Delete(..)
230 -- ** @UPDATE@
327 -- ** @UPDATE@
232 , Update(..)
328 , Update(..)
234 -- ** @.. RETURNING@
332 -- ** @.. RETURNING@
235 , Returning(..)
333 , Returning(..)
237− -- * TODO
335+ -- ** @CREATE VIEW@
238− -- TODO
336+ , createView
239− -- These need organizing, but are reachable from Rel8's documentation so we
337+ , createOrReplaceView
240− -- do need to export and document them.
338+
241− , HasNullability
339+ -- ** Sequences
242− , NotNull
340+ , nextval
243− , HTable
341+ , evaluate
@@ -250,64 +346,94 @@ import Prelude ()
252import Rel8.Aggregate
348import 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
253import Rel8.Expr
358import Rel8.Expr
254import Rel8.Expr.Aggregate
359import Rel8.Expr.Aggregate
255import Rel8.Expr.Bool
360import Rel8.Expr.Bool
361+import Rel8.Expr.Default
256import Rel8.Expr.Eq
362import Rel8.Expr.Eq
257import Rel8.Expr.Function
363import Rel8.Expr.Function
258import Rel8.Expr.Null
364import Rel8.Expr.Null
365+import Rel8.Expr.Opaleye (unsafeCastExpr, unsafeLiteral)
259import Rel8.Expr.Ord
366import Rel8.Expr.Ord
260import Rel8.Expr.Order
367import Rel8.Expr.Order
261import Rel8.Expr.Serialize
368import 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 )
262import Rel8.Order
373import Rel8.Order
263import Rel8.Query
374import Rel8.Query
264import Rel8.Query.Aggregate
375import Rel8.Query.Aggregate
265import Rel8.Query.Distinct
376import Rel8.Query.Distinct
266import Rel8.Query.Each
377import Rel8.Query.Each
267import Rel8.Query.Either
378import Rel8.Query.Either
379+import Rel8.Query.Evaluate
268import Rel8.Query.Exists
380import Rel8.Query.Exists
269import Rel8.Query.Filter
381import Rel8.Query.Filter
382+import Rel8.Query.Indexed
270import Rel8.Query.Limit
383import Rel8.Query.Limit
271import Rel8.Query.List
384import Rel8.Query.List
272import Rel8.Query.Maybe
385import Rel8.Query.Maybe
273import Rel8.Query.Null
386import Rel8.Query.Null
274import Rel8.Query.Order
387import Rel8.Query.Order
388+import Rel8.Query.Rebind
275import Rel8.Query.SQL (showQuery)
389import Rel8.Query.SQL (showQuery)
276import Rel8.Query.Set
390import Rel8.Query.Set
277import Rel8.Query.These
391import Rel8.Query.These
278import Rel8.Query.Values
392import Rel8.Query.Values
279−import Rel8.Schema.Column
393+import Rel8.Query.Window
280−import Rel8.Schema.Context.Label
394+import Rel8.Schema.Field
281−import Rel8.Schema.Generic
282import Rel8.Schema.HTable
395import Rel8.Schema.HTable
283import Rel8.Schema.Name
396import Rel8.Schema.Name
284−import Rel8.Schema.Nullability
397+import Rel8.Schema.Null hiding ( nullable )
398+import Rel8.Schema.Result ( Result )
285import Rel8.Schema.Table
399import Rel8.Schema.Table
286import Rel8.Statement.Delete
400import Rel8.Statement.Delete
287import Rel8.Statement.Insert
401import Rel8.Statement.Insert
402+import Rel8.Statement.OnConflict
288import Rel8.Statement.Returning
403import Rel8.Statement.Returning
289import Rel8.Statement.Select
404import Rel8.Statement.Select
405+import Rel8.Statement.SQL
290import Rel8.Statement.Update
406import Rel8.Statement.Update
407+import Rel8.Statement.View
291import Rel8.Table
408import Rel8.Table
409+import Rel8.Table.ADT
292import Rel8.Table.Aggregate
410import Rel8.Table.Aggregate
293import Rel8.Table.Alternative
411import Rel8.Table.Alternative
294import Rel8.Table.Bool
412import Rel8.Table.Bool
295import Rel8.Table.Either
413import Rel8.Table.Either
296import Rel8.Table.Eq
414import Rel8.Table.Eq
297−import Rel8.Table.Insert
415+import Rel8.Table.HKD
298import Rel8.Table.List
416import Rel8.Table.List
299import Rel8.Table.Maybe
417import Rel8.Table.Maybe
300import Rel8.Table.Name
418import Rel8.Table.Name
301import Rel8.Table.NonEmpty
419import Rel8.Table.NonEmpty
420+import Rel8.Table.Null
421+import Rel8.Table.Opaleye ( castTable )
302import Rel8.Table.Ord
422import Rel8.Table.Ord
303import Rel8.Table.Order
423import Rel8.Table.Order
424+import Rel8.Table.Projection
425+import Rel8.Table.Rel8able ()
304import Rel8.Table.Serialize
426import Rel8.Table.Serialize
305import Rel8.Table.These
427import Rel8.Table.These
428+import Rel8.Table.Transpose
429+import Rel8.Table.Window
306import Rel8.Type
430import Rel8.Type
431+import Rel8.Type.Composite
307import Rel8.Type.Eq
432import Rel8.Type.Eq
433+import Rel8.Type.Enum
308import Rel8.Type.Information
434import Rel8.Type.Information
309−import Rel8.Type.JSONEncoded
310import Rel8.Type.JSONBEncoded
435import Rel8.Type.JSONBEncoded
436+import Rel8.Type.JSONEncoded
311import Rel8.Type.Monoid
437import Rel8.Type.Monoid
312import Rel8.Type.Num
438import Rel8.Type.Num
313import Rel8.Type.Ord
439import Rel8.Type.Ord
@@ -315,362 +441,216 @@ import Rel8.Type.ReadShow
315import Rel8.Type.Semigroup
441import Rel8.Type.Semigroup
316import Rel8.Type.String
442import Rel8.Type.String
317import Rel8.Type.Sum
443import Rel8.Type.Sum
318−import Rel8.Expr.Opaleye (unsafeCastExpr)
444+import Rel8.Window
323−-- In this section, we'll take a look at using Rel8 to work with a small
324−-- database for Haskell packages. We'll take a look at idiomatic usage of Rel8,
325−-- mapping tables to Haskell, and then look at writing some simple queries.
327−-- The documentation in this library uses the following language extensions:
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)
383−-- Now we can write our final schema mapping. First, the @author@ table:
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
395−-- And similarly, the @project@ table:
398−-- data Project f = Project
399−-- { projectAuthorId :: Column f AuthorId
400−-- , projectName :: Column f Text
402−-- deriving stock Generic
403−-- deriving anyclass HigherKindedTable
406−-- To show query results in this documentation, we'll also need @Show@
407−-- instances: Unfortunately these definitions look a bit scary, but they are
408−-- essentially just @deriving (Show)@:
410−-- >>> deriving stock instance f ~ Identity => Show (Author f)
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.
465−-- >>> connectionString <- System.Environment.getEnv "TEST_DATABASE_URL"
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:
528−-- >>> select c $ projectName <$> each projectSchema
529−-- ["rel8","aeson","text"]
533−-- Another common operation in relational databases is to take the @JOIN@ of
534−-- multiple tables. Rel8 doesn't have a specific join operation, but we can
535−-- recover the functionality of a join by selecting all rows of two tables, and
536−-- then using 'where_' to filter them.
538−-- To see how this works, first let's look at taking the product of two tables.
539−-- We can do this by simply calling 'each' twice, and then returning a tuple of
543−-- mapM_ print =<< select c do
544−-- author <- each authorSchema
545−-- project <- each projectSchema
546−-- return (projectName project, authorName author)
549−-- ("rel8","Bryan O'Sullivan")
550−-- ("rel8","Emily Pillmore")
552−-- ("aeson","Bryan O'Sullivan")
553−-- ("aeson","Emily Pillmore")
555−-- ("text","Bryan O'Sullivan")
556−-- ("text","Emily Pillmore")
558−-- This isn't quite right, though, as we have ended up pairing up the wrong
559−-- projects and authors. To fix this, we can use 'where_' to restrict the
560−-- returned rows. We could write:
563−-- > author <- each authorSchema
564−-- > project <- each projectSchema
565−-- > where_ $ projectAuthorId project ==. authorId author
566−-- > return (project, author)
568−-- but doing this every time you need a join can obscure the meaning of the
569−-- query you're writing. A good practice is to introduce specialised functions
570−-- for the particular joins in your database. In our case, this would be:
573−-- projectsForAuthor :: Author Expr -> Query (Project Expr)
574−-- projectsForAuthor a = each projectSchema >>= filter \p ->
575−-- projectAuthorId p ==. authorId a
578−-- Our final query is then:
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")
592−-- Rel8 is also capable of performing @LEFT JOIN@s. To perform @LEFT JOIN@s, we
593−-- follow the same approach as before, but use the 'optional' query transformer
594−-- to allow for the possibility of the join to fail.
596−-- In our test database, we can see that there's another author who we haven't
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)
612−-- ("Ollie",Just "rel8")
613−-- ("Bryan O'Sullivan",Just "aeson")
614−-- ("Bryan O'Sullivan",Just "text")
615−-- ("Emily Pillmore",Nothing)
617−-- $guideAggregation
446+
619−-- Aggregations are operations like @sum@ and @count@ - operations that reduce
448+-- To run queries and otherwise interact with a PostgreSQL database, Rel8
620−-- multiple rows to single values. To perform aggregations in Rel8, we can use
449+-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
621−-- the 'aggregate' function, which takes a 'Query' of aggregated expressions,
450+-- 'insert', 'update' and 'delete' will generally need the
622−-- runs the aggregation, and returns aggregated rows.
451+-- `DuplicateRecordFields` language extension enabled.
624−-- To start, let's look at a simple aggregation that tells us how many projects
453+-- $adts
625−-- exist:
454+-- Algebraic data types can be modelled between Haskell and SQL.
629−-- Rel8 is also capable of aggregating multiple rows into a single row by
630−-- concatenating all rows as a list. This aggregation allows us to break free
631−-- of the row-orientated nature of SQL and write queries that return tree-like
632−-- structures. Earlier we saw an example of returning authors with their
633−-- projects, but the query didn't do a great job of describing the one-to-many
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
645−-- let authorsAndProjects = do
646−-- author <- each authorSchema
647−-- project <- projectsForAuthor author
648−-- return (author, project)
652−-- >>> :t select c authorsAndProjects
456+-- * Your SQL table needs a certain text field that tags which Haskell constructor is in use.
653−-- select c authorsAndProjects
457+-- * You have to use a few combinators to specify the sum type's individual constructors.
654−-- :: MonadIO m => m [(Author Identity, Project Identity)]
458+-- * If you want to do case analysis at the @Expr@ (SQL) level, you can use 'maybe'/'either'-like eliminators.
656−-- Our query gives us a single list of pairs of authors and projects. However,
460+-- The documentation in this section will assume a set of database types like this:
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])]
462+-- @
463+-- data Thing f = ThingEmployer (Employer f) | ThingPotato (Potato f) | Nullary
464+-- deriving stock Generic
663−-- This would be a much better type! Rel8 can produce a query with this type by
466+-- data Employer f = Employer { employerId :: f Int32, employerName :: f Text}
664−-- simply wrapping the call to @projectsForAuthor@ with either 'some' or
467+-- deriving stock Generic
665−-- 'many'. Here we'll use 'many', which allows for the possibility of an author
468+-- deriving anyclass Rel8able
666−-- to have no projects:
668−-- >>> :{
470+-- data Potato f = Potato { size :: f Int32, grower :: f Text }
669−-- mapM_ print =<< select c do
471+-- deriving stock Generic
670−-- author <- each authorSchema
472+-- deriving anyclass Rel8able
671−-- projectNames <- many $ projectName <$> projectsForAuthor author
473+-- @
672−-- return (authorName author, projectNames)
474+
477+-- First, in your 'TableSchema', name your type like this:
480+-- thingSchema :: TableSchema (ADT Thing Name)
483+-- { schema = Nothing,
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',
503+-- which is also variadic, and has one argument for each
504+-- constructor. Similar to 'maybe'.
507+-- query :: Query (ADT Thing Expr)
509+-- thingExpr <- each thingSchema
511+-- deconstructADT @Thing
512+-- (\employer -> employerName employer ==. lit \"Mary\")
513+-- (\potato -> grower potato ==. lit \"Mary\")
514+-- (lit False) -- Nullary case
523+-- CAST("tag0_1" AS text) as "tag",
524+-- CAST("id1_1" AS int4) as "ThingEmployer/_1/employerId",
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"
531+-- "tag" as "tag0_1",
533+-- "name" as "name2_1",
534+-- "size" as "size3_1",
535+-- "Mary" as "Mary4_1"
536+-- FROM "thing" as "T1") as "T1"
537+-- WHERE (CASE WHEN ("tag0_1") = (CAST(E'ThingPotato' AS text)) THEN ("Mary4_1") = (CAST(E'Mary' AS text))
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"
543+-- To construct an ADT, you can use 'buildADT' or 'constructADT'. Consider the following type:
546+-- data Task f = Pending | Complete (CompletedTask f)
549+-- 'buildADT' is for constructing values of 'Task' in the 'Expr'
550+-- context. 'buildADT' needs two type-level arguments before its type
551+-- makes any sense. The first argument is the type of the "ADT", which
552+-- in our case is 'Task'. The second is the name of the constructor we
553+-- want to use. So that means we have the following possible
554+-- instantiations of 'buildADT' for 'Task':
557+-- > :t buildADT @Task @\"Pending\"
558+-- buildADT @Task @\"Pending\" :: ADT Task Expr
559+-- > :t buildADT @Task @\"Complete\"
560+-- buildADT @Task @\"Complete\" :: CompletedTask Expr -> ADT Task Expr
563+-- Note that as the "Pending" constructor has no fields, @buildADT
564+-- \@Task \@"Pending"@ is equivalent to @lit Pending@. But @buildADT
565+-- \@Task \@"Complete"@ is not the same as @lit . Complete@:
568+-- > :t lit . Complete
569+-- lit . Complete :: CompletedTask Result -> ADT Task Expr
573+-- Note that the former takes a @CompletedTask Expr@ while the latter
574+-- takes a @CompletedTask Result@. The former is more powerful because
575+-- you can construct @Task@s using dynamic values coming a database
578+-- To show what this can look like in SQL, consider:
582+-- showQuery $ values
583+-- [ buildADT @Task @\"Pending\"
584+-- , buildADT @Task @\"Complete\" CompletedTask {date = Rel8.Expr.Time.now}
674−-- ("Ollie",["rel8"])
587+-- @
675−-- ("Bryan O'Sullivan",["aeson","text"])
588+--
676−-- ("Emily Pillmore",[])
589+-- This produces the following SQL:
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.