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