@@ -38,6 +38,7 @@ module Rel8
3838 , HMaybe
3939 , HList
4040 , HNonEmpty
41+ , HNull
4142 , HThese
4243 , Lift
4344
@@ -46,16 +47,18 @@ module Rel8
4647 , Transposes
4748 , AltTable((<|>:))
4849 , AlternativeTable( emptyTable )
49− , EqTable, (==:), (/=:)
50− , OrdTable, ascTable, descTable
50+ , EqTable(..), (==:), (/=:)
51+ , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
5152 , lit
5253 , bool
5354 , case_
55+ , castTable
5456
5557 -- ** @MaybeTable@
5658 , MaybeTable
5759 , maybeTable, ($?), nothingTable, justTable
5860 , isNothingTable, isJustTable
61+ , fromMaybeTable
5962 , optional
6063 , catMaybeTable
6164 , traverseMaybeTable
@@ -78,6 +81,7 @@ module Rel8
7881 , isThisTable, isThatTable, isThoseTable
7982 , hasHereTable, hasThereTable
8083 , justHereTable, justThereTable
84+ , alignMaybeTable
8185 , alignBy
8286 , keepHereTable, loseHereTable
8387 , keepThereTable, loseThereTable
@@ -106,14 +110,37 @@ module Rel8
106110 , catNonEmptyTable
107111 , catNonEmpty
108112
109− -- ** @ADT@
113+ -- ** @NullTable@
114+ , NullTable
115+ , nullableTable, nullTable, nullifyTable
116+ , isNullTable, isNonNullTable
117+ , catNullTable
118+ , nameNullTable
119+ , toNullTable, toMaybeTable
120+
121+ -- ** Algebraic data types / sum types
122+ -- $adts
123+
124+ -- *** Naming of ADTs
125+ -- $naming
126+ , NameADT, nameADT
110127 , ADT, ADTable
128+
129+ -- *** Deconstruction of ADTs
130+ -- $deconstruction
131+ , DeconstructADT, deconstructADT
132+
133+ -- *** Construction of ADTs
134+ -- $construction
111135 , BuildADT, buildADT
112136 , ConstructADT, constructADT
113− , DeconstructADT, deconstructADT
114− , NameADT, nameADT
137+
138+ -- *** Other ADT operations
115139 , AggregateADT, aggregateADT
116140
141+ -- *** Miscellaneous notes
142+ -- $misc-notes
143+
117144 -- ** @HKD@
118145 , HKD, HKDable
119146 , BuildHKD, buildHKD
@@ -138,6 +165,7 @@ module Rel8
138165 -- ** @null@
139166 , NotNull
140167 , Nullable
168+ , Homonullable
141169 , null
142170 , nullify
143171 , nullable
@@ -156,6 +184,7 @@ module Rel8
156184 , (==.), (/=.), (==?), (/=?)
157185 , in_
158186 , boolExpr, caseExpr
187+ , like, ilike
159188
160189 -- ** Ordering
161190 , DBOrd
@@ -224,10 +253,11 @@ module Rel8
224253 , countRows
225254 , groupBy
226255 , listAgg, listAggExpr
256+ , mode
227257 , nonEmptyAgg, nonEmptyAggExpr
228258 , DBMax, max
229259 , DBMin, min
230− , DBSum, sum, sumWhere
260+ , DBSum, sum, sumWhere, avg
231261 , DBString, stringAgg
232262 , count
233263 , countStar
@@ -245,6 +275,26 @@ module Rel8
245275 , nullsLast
246276
247277 -- ** Window functions
278+ , Window
279+ , window
280+ , Partition
281+ , over
282+ , partitionBy
283+ , orderPartitionBy
284+ , cumulative
285+ , cumulative_
286+ , currentRow
287+ , rowNumber
288+ , rank
289+ , denseRank
290+ , percentRank
291+ , cumeDist
292+ , ntile
293+ , lag
294+ , lead
295+ , firstValue
296+ , lastValue
297+ , nthValue
248298 , indexed
249299
250300 -- ** Bindings
@@ -284,6 +334,7 @@ module Rel8
284334
285335 -- ** @CREATE VIEW@
286336 , createView
337+ , createOrReplaceView
287338
288339 -- ** Sequences
289340 , nextval
@@ -302,6 +353,7 @@ import Rel8.Column.Lift
302353import Rel8.Column.List
303354import Rel8.Column.Maybe
304355import Rel8.Column.NonEmpty
356+import Rel8.Column.Null
305357import Rel8.Column.These
306358import Rel8.Expr
307359import Rel8.Expr.Aggregate
@@ -315,6 +367,8 @@ import Rel8.Expr.Ord
315367import Rel8.Expr.Order
316368import Rel8.Expr.Serialize
317369import Rel8.Expr.Sequence
370+import Rel8.Expr.Text ( like, ilike )
371+import Rel8.Expr.Window hiding ( cumulative )
318372import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
319373import Rel8.Order
320374import Rel8.Query
@@ -336,6 +390,7 @@ import Rel8.Query.SQL (showQuery)
336390import Rel8.Query.Set
337391import Rel8.Query.These
338392import Rel8.Query.Values
393+import Rel8.Query.Window
339394import Rel8.Schema.Field
340395import Rel8.Schema.HTable
341396import Rel8.Schema.Name
@@ -362,6 +417,8 @@ import Rel8.Table.List
362417import Rel8.Table.Maybe
363418import Rel8.Table.Name
364419import Rel8.Table.NonEmpty
420+import Rel8.Table.Null
421+import Rel8.Table.Opaleye ( castTable )
365422import Rel8.Table.Ord
366423import Rel8.Table.Order
367424import Rel8.Table.Projection
@@ -369,6 +426,7 @@ import Rel8.Table.Rel8able ()
369426import Rel8.Table.Serialize
370427import Rel8.Table.These
371428import Rel8.Table.Transpose
429+import Rel8.Table.Window
372430import Rel8.Type
373431import Rel8.Type.Composite
374432import Rel8.Type.Eq
@@ -383,6 +441,7 @@ import Rel8.Type.ReadShow
383441import Rel8.Type.Semigroup
384442import Rel8.Type.String
385443import Rel8.Type.Sum
444+import Rel8.Window
386445
387446
388447-- $running
@@ -390,3 +449,208 @@ import Rel8.Type.Sum
390449-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
391450-- 'insert', 'update' and 'delete' will generally need the
392451-- `DuplicateRecordFields` language extension enabled.
452+
453+-- $adts
454+-- Algebraic data types can be modelled between Haskell and SQL.
455+--
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.
459+--
460+-- The documentation in this section will assume a set of database types like this:
461+--
462+-- @
463+-- data Thing f = ThingEmployer (Employer f) | ThingPotato (Potato f) | Nullary
464+-- deriving stock Generic
465+--
466+-- data Employer f = Employer { employerId :: f Int32, employerName :: f Text}
467+-- deriving stock Generic
468+-- deriving anyclass Rel8able
469+--
470+-- data Potato f = Potato { size :: f Int32, grower :: f Text }
471+-- deriving stock Generic
472+-- deriving anyclass Rel8able
473+-- @
474+
475+-- $naming
476+--
477+-- First, in your 'TableSchema', name your type like this:
478+--
479+-- @
480+-- thingSchema :: TableSchema (ADT Thing Name)
481+-- thingSchema =
482+-- TableSchema
483+-- { schema = Nothing,
484+-- name = \"thing\",
485+-- columns =
486+-- nameADT @Thing
487+-- \"tag\"
488+-- Employer
489+-- { employerName = \"name\",
490+-- employerId = \"id\"
491+-- }
492+-- Potato {size = \"size\", grower = \"Mary\"}
493+-- }
494+-- @
495+--
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.
499+
500+-- $deconstruction
501+--
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'.
505+--
506+-- @
507+-- query :: Query (ADT Thing Expr)
508+-- query = do
509+-- thingExpr <- each thingSchema
510+-- where_ $
511+-- deconstructADT @Thing
512+-- (\employer -> employerName employer ==. lit \"Mary\")
513+-- (\potato -> grower potato ==. lit \"Mary\")
514+-- (lit False) -- Nullary case
515+-- thingExpr
516+-- pure thingExpr
517+-- @
518+--
519+-- SQL output:
520+--
521+-- @
522+-- SELECT
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"
528+-- FROM (SELECT
529+-- *
530+-- FROM (SELECT
531+-- "tag" as "tag0_1",
532+-- "id" as "id1_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"
539+-- @
540+
541+-- $construction
542+--
543+-- To construct an ADT, you can use 'buildADT' or 'constructADT'. Consider the following type:
544+--
545+-- @
546+-- data Task f = Pending | Complete (CompletedTask f)
547+-- @
548+--
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':
555+--
556+-- @
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
561+-- @
562+--
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@:
566+--
567+-- @
568+-- > :t lit . Complete
569+-- lit . Complete :: CompletedTask Result -> ADT Task Expr
570+-- @
571+--
572+--
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
576+-- query.
577+--
578+-- To show what this can look like in SQL, consider:
579+--
580+-- @
581+-- > :{
582+-- showQuery $ values
583+-- [ buildADT @Task @\"Pending\"
584+-- , buildADT @Task @\"Complete\" CompletedTask {date = Rel8.Expr.Time.now}
585+-- ]
586+-- :}
587+-- @
588+--
589+-- This produces the following SQL:
590+--
591+-- @
592+-- SELECT
593+-- CAST(\"values0_1\" AS text) as \"tag\",
594+-- CAST(\"values1_1\" AS timestamptz) as \"Complete/_1/date\"
595+-- FROM (SELECT
596+-- *
597+-- FROM (SELECT \"column1\" as \"values0_1\",
598+-- \"column2\" as \"values1_1\"
599+-- FROM
600+-- (VALUES
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\"
603+-- @
604+--
605+-- This is what you get if you run it in @psql@:
606+--
607+--
608+-- @
609+-- tag | Complete/_1/date
610+-- ----------+-------------------------------
611+-- Pending |
612+-- Complete | 2022-05-19 21:28:23.969065+00
613+-- (2 rows)
614+-- @
615+--
616+-- "constructADT" is less convenient but more general alternative to
617+-- "buildADT". It requires only one type-level argument for its type
618+-- to make sense:
619+--
620+-- @
621+-- > :t constructADT @Task
622+-- constructADT @Task
623+-- :: (forall r. r -> (CompletedTask Expr -> r) -> r) -> ADT Task Expr
624+-- @
625+--
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:
629+--
630+-- @
631+-- let
632+-- pending :: ADT Task Expr
633+-- pending = constructADT @Task $ \pending _complete -> pending
634+--
635+-- complete :: ADT Task Expr
636+-- complete = constructADT @Task $ \_pending complete -> complete CompletedTask {date = Rel8.Expr.Time.now}
637+-- @
638+--
639+-- These values are otherwise identical to the ones we saw above with
640+-- @buildADT@, it's just a different style of constructing them.
641+--
642+
643+-- $misc-notes
644+--
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.
650+--
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.