@@ -38,6 +38,7 @@ module Rel8
40 , HNonEmpty
40 , HNonEmpty
@@ -46,16 +47,18 @@ module Rel8
46 , Transposes
47 , Transposes
47 , AltTable((<|>:))
48 , AltTable((<|>:))
48 , AlternativeTable( emptyTable )
49 , AlternativeTable( emptyTable )
49− , EqTable, (==:), (/=:)
50+ , EqTable(..), (==:), (/=:)
50− , OrdTable, ascTable, descTable
51+ , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
55 -- ** @MaybeTable@
57 -- ** @MaybeTable@
56 , MaybeTable
58 , MaybeTable
57 , maybeTable, ($?), nothingTable, justTable
59 , maybeTable, ($?), nothingTable, justTable
58 , isNothingTable, isJustTable
60 , isNothingTable, isJustTable
59 , optional
62 , optional
60 , catMaybeTable
63 , catMaybeTable
61 , traverseMaybeTable
64 , traverseMaybeTable
@@ -78,6 +81,7 @@ 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
@@ -106,14 +110,37 @@ module Rel8
106 , catNonEmptyTable
110 , catNonEmptyTable
107 , catNonEmpty
111 , catNonEmpty
109− -- ** @ADT@
113+ -- ** @NullTable@
115+ , nullableTable, nullTable, nullifyTable
116+ , isNullTable, isNonNullTable
119+ , toNullTable, toMaybeTable
121+ -- ** Algebraic data types / sum types
124+ -- *** Naming of ADTs
110 , ADT, ADTable
127 , ADT, ADTable
129+ -- *** Deconstruction of ADTs
131+ , DeconstructADT, deconstructADT
133+ -- *** Construction of ADTs
111 , BuildADT, buildADT
135 , BuildADT, buildADT
112 , ConstructADT, constructADT
136 , ConstructADT, constructADT
113− , DeconstructADT, deconstructADT
137+
114− , NameADT, nameADT
138+ -- *** Other ADT operations
115 , AggregateADT, aggregateADT
139 , AggregateADT, aggregateADT
141+ -- *** Miscellaneous notes
117 -- ** @HKD@
144 -- ** @HKD@
118 , HKD, HKDable
145 , HKD, HKDable
119 , BuildHKD, buildHKD
146 , BuildHKD, buildHKD
@@ -138,6 +165,7 @@ module Rel8
138 -- ** @null@
165 -- ** @null@
139 , NotNull
166 , NotNull
140 , Nullable
167 , Nullable
142 , nullify
170 , nullify
143 , nullable
171 , nullable
@@ -156,6 +184,7 @@ module Rel8
156 , (==.), (/=.), (==?), (/=?)
184 , (==.), (/=.), (==?), (/=?)
158 , boolExpr, caseExpr
186 , boolExpr, caseExpr
160 -- ** Ordering
189 -- ** Ordering
@@ -224,10 +253,11 @@ module Rel8
224 , countRows
253 , countRows
225 , groupBy
254 , groupBy
226 , listAgg, listAggExpr
255 , listAgg, listAggExpr
227 , nonEmptyAgg, nonEmptyAggExpr
257 , nonEmptyAgg, nonEmptyAggExpr
228 , DBMax, max
258 , DBMax, max
229 , DBMin, min
259 , DBMin, min
230− , DBSum, sum, sumWhere
260+ , DBSum, sum, sumWhere, avg
231 , DBString, stringAgg
261 , DBString, stringAgg
233 , countStar
263 , countStar
@@ -245,6 +275,26 @@ module Rel8
245 , nullsLast
275 , nullsLast
247 -- ** Window functions
277 -- ** Window functions
248 , indexed
298 , indexed
250 -- ** Bindings
300 -- ** Bindings
@@ -284,6 +334,7 @@ module Rel8
285 -- ** @CREATE VIEW@
335 -- ** @CREATE VIEW@
286 , createView
336 , createView
337+ , createOrReplaceView
288 -- ** Sequences
339 -- ** Sequences
289 , nextval
340 , nextval
@@ -302,6 +353,7 @@ import Rel8.Column.Lift
302import Rel8.Column.List
353import Rel8.Column.List
303import Rel8.Column.Maybe
354import Rel8.Column.Maybe
304import Rel8.Column.NonEmpty
355import Rel8.Column.NonEmpty
356+import Rel8.Column.Null
305import Rel8.Column.These
357import Rel8.Column.These
306import Rel8.Expr
358import Rel8.Expr
307import Rel8.Expr.Aggregate
359import Rel8.Expr.Aggregate
@@ -315,6 +367,8 @@ import Rel8.Expr.Ord
315import Rel8.Expr.Order
367import Rel8.Expr.Order
316import Rel8.Expr.Serialize
368import Rel8.Expr.Serialize
317import Rel8.Expr.Sequence
369import Rel8.Expr.Sequence
370+import Rel8.Expr.Text ( like, ilike )
371+import Rel8.Expr.Window hiding ( cumulative )
318import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
372import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
319import Rel8.Order
373import Rel8.Order
320import Rel8.Query
374import Rel8.Query
@@ -336,6 +390,7 @@ import Rel8.Query.SQL (showQuery)
336import Rel8.Query.Set
390import Rel8.Query.Set
337import Rel8.Query.These
391import Rel8.Query.These
338import Rel8.Query.Values
392import Rel8.Query.Values
393+import Rel8.Query.Window
339import Rel8.Schema.Field
394import Rel8.Schema.Field
340import Rel8.Schema.HTable
395import Rel8.Schema.HTable
341import Rel8.Schema.Name
396import Rel8.Schema.Name
@@ -362,6 +417,8 @@ import Rel8.Table.List
362import Rel8.Table.Maybe
417import Rel8.Table.Maybe
363import Rel8.Table.Name
418import Rel8.Table.Name
364import Rel8.Table.NonEmpty
419import Rel8.Table.NonEmpty
420+import Rel8.Table.Null
421+import Rel8.Table.Opaleye ( castTable )
365import Rel8.Table.Ord
422import Rel8.Table.Ord
366import Rel8.Table.Order
423import Rel8.Table.Order
367import Rel8.Table.Projection
424import Rel8.Table.Projection
@@ -369,6 +426,7 @@ import Rel8.Table.Rel8able ()
369import Rel8.Table.Serialize
426import Rel8.Table.Serialize
370import Rel8.Table.These
427import Rel8.Table.These
371import Rel8.Table.Transpose
428import Rel8.Table.Transpose
429+import Rel8.Table.Window
372import Rel8.Type
430import Rel8.Type
373import Rel8.Type.Composite
431import Rel8.Type.Composite
374import Rel8.Type.Eq
432import Rel8.Type.Eq
@@ -383,6 +441,7 @@ import Rel8.Type.ReadShow
383import Rel8.Type.Semigroup
441import Rel8.Type.Semigroup
384import Rel8.Type.String
442import Rel8.Type.String
385import Rel8.Type.Sum
443import Rel8.Type.Sum
388-- $running
447-- $running
@@ -390,3 +449,208 @@ import Rel8.Type.Sum
390-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
449-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
391-- 'insert', 'update' and 'delete' will generally need the
450-- 'insert', 'update' and 'delete' will generally need the
392-- `DuplicateRecordFields` language extension enabled.
451-- `DuplicateRecordFields` language extension enabled.
454+-- Algebraic data types can be modelled between Haskell and SQL.
456+-- * Your SQL table needs a certain text field that tags which Haskell constructor is in use.
457+-- * You have to use a few combinators to specify the sum type's individual constructors.
458+-- * If you want to do case analysis at the @Expr@ (SQL) level, you can use 'maybe'/'either'-like eliminators.
460+-- The documentation in this section will assume a set of database types like this:
463+-- data Thing f = ThingEmployer (Employer f) | ThingPotato (Potato f) | Nullary
464+-- deriving stock Generic
466+-- data Employer f = Employer { employerId :: f Int32, employerName :: f Text}
467+-- deriving stock Generic
468+-- deriving anyclass Rel8able
470+-- data Potato f = Potato { size :: f Int32, grower :: f Text }
471+-- deriving stock Generic
472+-- deriving anyclass Rel8able
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}
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.