@@ -38,6 +38,7 @@ module Rel8
3838 , HMaybe
3939 , HList
4040 , HNonEmpty
41+ , HNull
4142 , HThese
4243 , Lift
4344
@@ -46,8 +47,8 @@ module Rel8
4647 , Transposes
4748 , AltTable((<|>:))
4849 , AlternativeTable( emptyTable )
49− , EqTable, (==:), (/=:)
50− , OrdTable, (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
50+ , EqTable(..), (==:), (/=:)
51+ , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
5152 , lit
5253 , bool
5354 , case_
@@ -57,6 +58,7 @@ module Rel8
5758 , MaybeTable
5859 , maybeTable, ($?), nothingTable, justTable
5960 , isNothingTable, isJustTable
61+ , fromMaybeTable
6062 , optional
6163 , catMaybeTable
6264 , traverseMaybeTable
@@ -79,6 +81,7 @@ module Rel8
7981 , isThisTable, isThatTable, isThoseTable
8082 , hasHereTable, hasThereTable
8183 , justHereTable, justThereTable
84+ , alignMaybeTable
8285 , alignBy
8386 , keepHereTable, loseHereTable
8487 , keepThereTable, loseThereTable
@@ -107,14 +110,37 @@ module Rel8
107110 , catNonEmptyTable
108111 , catNonEmpty
109112
110− -- ** @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
111127 , ADT, ADTable
128+
129+ -- *** Deconstruction of ADTs
130+ -- $deconstruction
131+ , DeconstructADT, deconstructADT
132+
133+ -- *** Construction of ADTs
134+ -- $construction
112135 , BuildADT, buildADT
113136 , ConstructADT, constructADT
114− , DeconstructADT, deconstructADT
115− , NameADT, nameADT
137+
138+ -- *** Other ADT operations
116139 , AggregateADT, aggregateADT
117140
141+ -- *** Miscellaneous notes
142+ -- $misc-notes
143+
118144 -- ** @HKD@
119145 , HKD, HKDable
120146 , BuildHKD, buildHKD
@@ -139,6 +165,7 @@ module Rel8
139165 -- ** @null@
140166 , NotNull
141167 , Nullable
168+ , Homonullable
142169 , null
143170 , nullify
144171 , nullable
@@ -157,6 +184,7 @@ module Rel8
157184 , (==.), (/=.), (==?), (/=?)
158185 , in_
159186 , boolExpr, caseExpr
187+ , like, ilike
160188
161189 -- ** Ordering
162190 , DBOrd
@@ -225,10 +253,11 @@ module Rel8
225253 , countRows
226254 , groupBy
227255 , listAgg, listAggExpr
256+ , mode
228257 , nonEmptyAgg, nonEmptyAggExpr
229258 , DBMax, max
230259 , DBMin, min
231− , DBSum, sum, sumWhere
260+ , DBSum, sum, sumWhere, avg
232261 , DBString, stringAgg
233262 , count
234263 , countStar
@@ -246,6 +275,26 @@ module Rel8
246275 , nullsLast
247276
248277 -- ** 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
249298 , indexed
250299
251300 -- ** Bindings
@@ -285,6 +334,7 @@ module Rel8
285334
286335 -- ** @CREATE VIEW@
287336 , createView
337+ , createOrReplaceView
288338
289339 -- ** Sequences
290340 , nextval
@@ -303,6 +353,7 @@ import Rel8.Column.Lift
303353import Rel8.Column.List
304354import Rel8.Column.Maybe
305355import Rel8.Column.NonEmpty
356+import Rel8.Column.Null
306357import Rel8.Column.These
307358import Rel8.Expr
308359import Rel8.Expr.Aggregate
@@ -316,6 +367,8 @@ import Rel8.Expr.Ord
316367import Rel8.Expr.Order
317368import Rel8.Expr.Serialize
318369import Rel8.Expr.Sequence
370+import Rel8.Expr.Text ( like, ilike )
371+import Rel8.Expr.Window hiding ( cumulative )
319372import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
320373import Rel8.Order
321374import Rel8.Query
@@ -337,6 +390,7 @@ import Rel8.Query.SQL (showQuery)
337390import Rel8.Query.Set
338391import Rel8.Query.These
339392import Rel8.Query.Values
393+import Rel8.Query.Window
340394import Rel8.Schema.Field
341395import Rel8.Schema.HTable
342396import Rel8.Schema.Name
@@ -363,6 +417,7 @@ import Rel8.Table.List
363417import Rel8.Table.Maybe
364418import Rel8.Table.Name
365419import Rel8.Table.NonEmpty
420+import Rel8.Table.Null
366421import Rel8.Table.Opaleye ( castTable )
367422import Rel8.Table.Ord
368423import Rel8.Table.Order
@@ -371,6 +426,7 @@ import Rel8.Table.Rel8able ()
371426import Rel8.Table.Serialize
372427import Rel8.Table.These
373428import Rel8.Table.Transpose
429+import Rel8.Table.Window
374430import Rel8.Type
375431import Rel8.Type.Composite
376432import Rel8.Type.Eq
@@ -385,6 +441,7 @@ import Rel8.Type.ReadShow
385441import Rel8.Type.Semigroup
386442import Rel8.Type.String
387443import Rel8.Type.Sum
444+import Rel8.Window
388445
389446
390447-- $running
@@ -392,3 +449,208 @@ import Rel8.Type.Sum
392449-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
393450-- 'insert', 'update' and 'delete' will generally need the
394451-- `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.