@@ -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
42− , Column, Field, Necessity( Required, Optional )
35+ , Column
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,14 +248,16 @@ module Rel8
183 -- ** Aggregation
249 -- ** Aggregation
184 , Aggregate
250 , Aggregate
185 , aggregate
252 , aggregate
186 , countRows
253 , countRows
187 , listAgg, listAggExpr
255 , listAgg, listAggExpr
188 , nonEmptyAgg, nonEmptyAggExpr
257 , nonEmptyAgg, nonEmptyAggExpr
190 , DBMax, max
258 , DBMax, max
191 , DBMin, min
259 , DBMin, min
192− , DBSum, sum, sumWhere
260+ , DBSum, sum, sumWhere, avg
193 , DBString, stringAgg
261 , DBString, stringAgg
195 , countStar
263 , countStar
@@ -206,43 +274,71 @@ module Rel8
206 , nullsFirst
274 , nullsFirst
207 , nullsLast
275 , nullsLast
277+ -- ** Window functions
210 , Serializable
304 , Serializable
212 -- * Running statements
308 -- * Running statements
213 -- ** @SELECT@
311 -- ** @SELECT@
216 -- ** @INSERT@
314 -- ** @INSERT@
217 , Insert(..)
315 , Insert(..)
218 , OnConflict(..)
316 , OnConflict(..)
220− , toInsert
319+ , unsafeDefault
221− , toInsertDefaults
320+ , showInsert
223 -- ** @DELETE@
322 -- ** @DELETE@
224 , Delete(..)
323 , Delete(..)
227 -- ** @UPDATE@
327 -- ** @UPDATE@
229 , Update(..)
328 , Update(..)
231 -- ** @.. RETURNING@
332 -- ** @.. RETURNING@
232 , Returning(..)
333 , Returning(..)
234 -- ** @CREATE VIEW@
335 -- ** @CREATE VIEW@
235 , createView
336 , createView
337+ , createOrReplaceView
237− -- * TODO
339+ -- ** Sequences
238− -- TODO
340+ , nextval
239− -- These need organizing, but are reachable from Rel8's documentation so we
341+ , evaluate
240− -- do need to export and document them.
@@ -250,65 +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
291import Rel8.Statement.View
407import Rel8.Statement.View
292import Rel8.Table
408import Rel8.Table
409+import Rel8.Table.ADT
293import Rel8.Table.Aggregate
410import Rel8.Table.Aggregate
294import Rel8.Table.Alternative
411import Rel8.Table.Alternative
295import Rel8.Table.Bool
412import Rel8.Table.Bool
296import Rel8.Table.Either
413import Rel8.Table.Either
297import Rel8.Table.Eq
414import Rel8.Table.Eq
298−import Rel8.Table.Insert
415+import Rel8.Table.HKD
299import Rel8.Table.List
416import Rel8.Table.List
300import Rel8.Table.Maybe
417import Rel8.Table.Maybe
301import Rel8.Table.Name
418import Rel8.Table.Name
302import Rel8.Table.NonEmpty
419import Rel8.Table.NonEmpty
420+import Rel8.Table.Null
421+import Rel8.Table.Opaleye ( castTable )
303import Rel8.Table.Ord
422import Rel8.Table.Ord
304import Rel8.Table.Order
423import Rel8.Table.Order
424+import Rel8.Table.Projection
425+import Rel8.Table.Rel8able ()
305import Rel8.Table.Serialize
426import Rel8.Table.Serialize
306import Rel8.Table.These
427import Rel8.Table.These
428+import Rel8.Table.Transpose
429+import Rel8.Table.Window
307import Rel8.Type
430import Rel8.Type
431+import Rel8.Type.Composite
308import Rel8.Type.Eq
432import Rel8.Type.Eq
433+import Rel8.Type.Enum
309import Rel8.Type.Information
434import Rel8.Type.Information
310−import Rel8.Type.JSONEncoded
311import Rel8.Type.JSONBEncoded
435import Rel8.Type.JSONBEncoded
436+import Rel8.Type.JSONEncoded
312import Rel8.Type.Monoid
437import Rel8.Type.Monoid
313import Rel8.Type.Num
438import Rel8.Type.Num
314import Rel8.Type.Ord
439import Rel8.Type.Ord
@@ -316,364 +441,216 @@ import Rel8.Type.ReadShow
316import Rel8.Type.Semigroup
441import Rel8.Type.Semigroup
317import Rel8.Type.String
442import Rel8.Type.String
318import Rel8.Type.Sum
443import Rel8.Type.Sum
319−import Rel8.Expr.Opaleye (unsafeCastExpr)
444+import Rel8.Window
320−import Rel8.Kind.Necessity
321−import Rel8.Schema.Field (Field)
326−-- In this section, we'll take a look at using Rel8 to work with a small
327−-- database for Haskell packages. We'll take a look at idiomatic usage of Rel8,
328−-- mapping tables to Haskell, and then look at writing some simple queries.
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)
386−-- Now we can write our final schema mapping. First, the @author@ table:
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
398−-- And similarly, the @project@ table:
401−-- data Project f = Project
402−-- { projectAuthorId :: Column f AuthorId
403−-- , projectName :: Column f Text
405−-- deriving stock Generic
406−-- deriving anyclass HigherKindedTable
409−-- To show query results in this documentation, we'll also need @Show@
410−-- instances: Unfortunately these definitions look a bit scary, but they are
411−-- essentially just @deriving (Show)@:
413−-- >>> deriving stock instance f ~ Identity => Show (Author f)
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.
468−-- >>> connectionString <- System.Environment.getEnv "TEST_DATABASE_URL"
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:
531−-- >>> select c $ projectName <$> each projectSchema
532−-- ["rel8","aeson","text"]
536−-- Another common operation in relational databases is to take the @JOIN@ of
537−-- multiple tables. Rel8 doesn't have a specific join operation, but we can
538−-- recover the functionality of a join by selecting all rows of two tables, and
539−-- then using 'where_' to filter them.
541−-- To see how this works, first let's look at taking the product of two tables.
542−-- We can do this by simply calling 'each' twice, and then returning a tuple of
546−-- mapM_ print =<< select c do
547−-- author <- each authorSchema
548−-- project <- each projectSchema
549−-- return (projectName project, authorName author)
552−-- ("rel8","Bryan O'Sullivan")
553−-- ("rel8","Emily Pillmore")
555−-- ("aeson","Bryan O'Sullivan")
556−-- ("aeson","Emily Pillmore")
558−-- ("text","Bryan O'Sullivan")
559−-- ("text","Emily Pillmore")
561−-- This isn't quite right, though, as we have ended up pairing up the wrong
562−-- projects and authors. To fix this, we can use 'where_' to restrict the
563−-- returned rows. We could write:
566−-- > author <- each authorSchema
567−-- > project <- each projectSchema
568−-- > where_ $ projectAuthorId project ==. authorId author
569−-- > return (project, author)
571−-- but doing this every time you need a join can obscure the meaning of the
572−-- query you're writing. A good practice is to introduce specialised functions
573−-- for the particular joins in your database. In our case, this would be:
576−-- projectsForAuthor :: Author Expr -> Query (Project Expr)
577−-- projectsForAuthor a = each projectSchema >>= filter \p ->
578−-- projectAuthorId p ==. authorId a
581−-- Our final query is then:
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")
595−-- Rel8 is also capable of performing @LEFT JOIN@s. To perform @LEFT JOIN@s, we
596−-- follow the same approach as before, but use the 'optional' query transformer
597−-- to allow for the possibility of the join to fail.
599−-- In our test database, we can see that there's another author who we haven't
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)
615−-- ("Ollie",Just "rel8")
616−-- ("Bryan O'Sullivan",Just "aeson")
617−-- ("Bryan O'Sullivan",Just "text")
618−-- ("Emily Pillmore",Nothing)
620−-- $guideAggregation
446+
622−-- Aggregations are operations like @sum@ and @count@ - operations that reduce
448+-- To run queries and otherwise interact with a PostgreSQL database, Rel8
623−-- multiple rows to single values. To perform aggregations in Rel8, we can use
449+-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
624−-- the 'aggregate' function, which takes a 'Query' of aggregated expressions,
450+-- 'insert', 'update' and 'delete' will generally need the
625−-- runs the aggregation, and returns aggregated rows.
451+-- `DuplicateRecordFields` language extension enabled.
627−-- To start, let's look at a simple aggregation that tells us how many projects
453+-- $adts
628−-- exist:
454+-- Algebraic data types can be modelled between Haskell and SQL.
632−-- Rel8 is also capable of aggregating multiple rows into a single row by
633−-- concatenating all rows as a list. This aggregation allows us to break free
634−-- of the row-orientated nature of SQL and write queries that return tree-like
635−-- structures. Earlier we saw an example of returning authors with their
636−-- projects, but the query didn't do a great job of describing the one-to-many
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
648−-- let authorsAndProjects = do
649−-- author <- each authorSchema
650−-- project <- projectsForAuthor author
651−-- return (author, project)
655−-- >>> :t select c authorsAndProjects
456+-- * Your SQL table needs a certain text field that tags which Haskell constructor is in use.
656−-- select c authorsAndProjects
457+-- * You have to use a few combinators to specify the sum type's individual constructors.
657−-- :: 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.
659−-- 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:
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])]
462+-- @
463+-- data Thing f = ThingEmployer (Employer f) | ThingPotato (Potato f) | Nullary
464+-- deriving stock Generic
666−-- 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}
667−-- simply wrapping the call to @projectsForAuthor@ with either 'some' or
467+-- deriving stock Generic
668−-- 'many'. Here we'll use 'many', which allows for the possibility of an author
468+-- deriving anyclass Rel8able
669−-- to have no projects:
671−-- >>> :{
470+-- data Potato f = Potato { size :: f Int32, grower :: f Text }
672−-- mapM_ print =<< select c do
471+-- deriving stock Generic
673−-- author <- each authorSchema
472+-- deriving anyclass Rel8able
674−-- projectNames <- many $ projectName <$> projectsForAuthor author
473+-- @
675−-- 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}
677−-- ("Ollie",["rel8"])
587+-- @
678−-- ("Bryan O'Sullivan",["aeson","text"])
588+--
679−-- ("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.