on da76712 help sign in
rel8/src/Rel8.hs
656 lines 15 kB history raw
1{-# language DuplicateRecordFields #-}
3module Rel8
4 ( -- * Database types
5 -- ** @DBType@
6 DBType(..)
8 -- *** Deriving-via helpers
9 -- **** @JSONEncoded@
10 , JSONEncoded(..)
11 , JSONBEncoded(..)
13 -- **** @ReadShow@
14 , ReadShow(..)
16 -- **** Generic
17 , Composite(..), DBComposite(..), compose, decompose
18 , Enum(..), DBEnum(..), Enumable
20 -- *** @TypeInformation@
21 , TypeInformation(..)
22 , mapTypeInformation
23 , parseTypeInformation
25 -- ** The @DBType@ hierarchy
26 , DBSemigroup(..)
27 , DBMonoid(..)
28 , DBNum
29 , DBIntegral
30 , DBFractional
31 , DBFloating
33 -- * Tables and higher-kinded tables
34 , Rel8able, KRel8able
35 , Column
36 , HADT
37 , HEither
38 , HMaybe
39 , HList
40 , HNonEmpty
41 , HNull
42 , HThese
43 , Lift
45 , Table(..)
46 , HTable
47 , Transposes
48 , AltTable((<|>:))
49 , AlternativeTable( emptyTable )
50 , EqTable(..), (==:), (/=:)
51 , OrdTable(..), (<:), (<=:), (>:), (>=:), ascTable, descTable, greatest, least
52 , lit
53 , bool
54 , case_
55 , castTable
57 -- ** @MaybeTable@
58 , MaybeTable
59 , maybeTable, ($?), nothingTable, justTable
60 , isNothingTable, isJustTable
61 , fromMaybeTable
62 , optional
63 , catMaybeTable
64 , traverseMaybeTable
65 , aggregateMaybeTable
66 , nameMaybeTable
68 -- ** @EitherTable@
69 , EitherTable
70 , eitherTable, leftTable, rightTable
71 , isLeftTable, isRightTable
72 , keepLeftTable
73 , keepRightTable
74 , bitraverseEitherTable
75 , aggregateEitherTable
76 , nameEitherTable
78 -- ** @TheseTable@
79 , TheseTable
80 , theseTable, thisTable, thatTable, thoseTable
81 , isThisTable, isThatTable, isThoseTable
82 , hasHereTable, hasThereTable
83 , justHereTable, justThereTable
84 , alignMaybeTable
85 , alignBy
86 , keepHereTable, loseHereTable
87 , keepThereTable, loseThereTable
88 , keepThisTable, loseThisTable
89 , keepThatTable, loseThatTable
90 , keepThoseTable, loseThoseTable
91 , bitraverseTheseTable
92 , aggregateTheseTable
93 , nameTheseTable
95 -- ** @ListTable@
96 , ListTable
97 , listTable, ($*)
98 , nameListTable
99 , many
100 , manyExpr
101 , catListTable
102 , catList
104 -- ** @NonEmptyTable@
105 , NonEmptyTable
106 , nonEmptyTable, ($+)
107 , nameNonEmptyTable
108 , some
109 , someExpr
110 , catNonEmptyTable
111 , catNonEmpty
113 -- ** @NullTable@
114 , NullTable
115 , nullableTable, nullTable, nullifyTable
116 , isNullTable, isNonNullTable
117 , catNullTable
118 , nameNullTable
119 , toNullTable, toMaybeTable
121 -- ** Algebraic data types / sum types
122 -- $adts
124 -- *** Naming of ADTs
125 -- $naming
126 , NameADT, nameADT
127 , ADT, ADTable
129 -- *** Deconstruction of ADTs
130 -- $deconstruction
131 , DeconstructADT, deconstructADT
133 -- *** Construction of ADTs
134 -- $construction
135 , BuildADT, buildADT
136 , ConstructADT, constructADT
138 -- *** Other ADT operations
139 , AggregateADT, aggregateADT
141 -- *** Miscellaneous notes
142 -- $misc-notes
144 -- ** @HKD@
145 , HKD, HKDable
146 , BuildHKD, buildHKD
147 , ConstructHKD, constructHKD
148 , DeconstructHKD, deconstructHKD
149 , NameHKD, nameHKD
150 , AggregateHKD, aggregateHKD
152 -- ** Table schemas
153 , TableSchema(..)
154 , Name
155 , namesFromLabels
156 , namesFromLabelsWith
158 -- * Expressions
159 , Expr
160 , Sql
161 , litExpr
162 , unsafeCastExpr
163 , unsafeLiteral
165 -- ** @null@
166 , NotNull
167 , Nullable
168 , Homonullable
169 , null
170 , nullify
171 , nullable
172 , isNull
173 , isNonNull
174 , mapNull
175 , liftOpNull
176 , catNull
177 , coalesce
179 -- ** Boolean operations
180 , DBEq
181 , true, false, not_
182 , (&&.), and_
183 , (||.), or_
184 , (==.), (/=.), (==?), (/=?)
185 , in_
186 , boolExpr, caseExpr
187 , like, ilike
189 -- ** Ordering
190 , DBOrd
191 , (<.), (<=.), (>.), (>=.)
192 , (<?), (<=?), (>?), (>=?)
193 , leastExpr, greatestExpr
195 -- ** Functions
196 , Function
197 , function
198 , nullaryFunction
199 , binaryOperator
201 -- * Queries
202 , Query
203 , showQuery
205 -- ** Projection
206 , Projection
207 , Projectable( project )
208 , Biprojectable( biproject )
209 , Projecting
210 , Field
212 -- ** Selecting rows
213 , Selects
214 , each
215 , values
217 -- ** Filtering
218 , filter
219 , where_
220 , present
221 , absent
222 , distinct
223 , distinctOn
224 , distinctOnBy
226 -- ** @LIMIT@/@OFFSET@
227 , limit
228 , offset
230 -- ** @UNION@
231 , union
232 , unionAll
234 -- ** @INTERSECT@
235 , intersect
236 , intersectAll
238 -- ** @EXCEPT@
239 , except
240 , exceptAll
242 -- ** @EXISTS@
243 , exists
244 , with
245 , withBy
246 , without
247 , withoutBy
249 -- ** Aggregation
250 , Aggregate
251 , Aggregates
252 , aggregate
253 , countRows
254 , groupBy
255 , listAgg, listAggExpr
256 , mode
257 , nonEmptyAgg, nonEmptyAggExpr
258 , DBMax, max
259 , DBMin, min
260 , DBSum, sum, sumWhere, avg
261 , DBString, stringAgg
262 , count
263 , countStar
264 , countDistinct
265 , countWhere
266 , and
267 , or
269 -- ** Ordering
270 , orderBy
271 , Order
272 , asc
273 , desc
274 , nullsFirst
275 , nullsLast
277 -- ** 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
298 , indexed
300 -- ** Bindings
301 , rebind
303 -- * IO
304 , Serializable
305 , ToExprs
306 , Result
308 -- * Running statements
309 -- $running
311 -- ** @SELECT@
312 , select
314 -- ** @INSERT@
315 , Insert(..)
316 , OnConflict(..)
317 , Upsert(..)
318 , insert
319 , unsafeDefault
320 , showInsert
322 -- ** @DELETE@
323 , Delete(..)
324 , delete
325 , showDelete
327 -- ** @UPDATE@
328 , Update(..)
329 , update
330 , showUpdate
332 -- ** @.. RETURNING@
333 , Returning(..)
335 -- ** @CREATE VIEW@
336 , createView
337 , createOrReplaceView
339 -- ** Sequences
340 , nextval
341 , evaluate
342 ) where
344-- base
345import Prelude ()
347-- rel8
348import Rel8.Aggregate
349import Rel8.Column
350import Rel8.Column.ADT
351import Rel8.Column.Either
352import Rel8.Column.Lift
353import Rel8.Column.List
354import Rel8.Column.Maybe
355import Rel8.Column.NonEmpty
356import Rel8.Column.Null
357import Rel8.Column.These
358import Rel8.Expr
359import Rel8.Expr.Aggregate
360import Rel8.Expr.Bool
361import Rel8.Expr.Default
362import Rel8.Expr.Eq
363import Rel8.Expr.Function
364import Rel8.Expr.Null
365import Rel8.Expr.Opaleye (unsafeCastExpr, unsafeLiteral)
366import Rel8.Expr.Ord
367import Rel8.Expr.Order
368import Rel8.Expr.Serialize
369import Rel8.Expr.Sequence
370import Rel8.Expr.Text ( like, ilike )
371import Rel8.Expr.Window hiding ( cumulative )
372import Rel8.Generic.Rel8able ( KRel8able, Rel8able )
373import Rel8.Order
374import Rel8.Query
375import Rel8.Query.Aggregate
376import Rel8.Query.Distinct
377import Rel8.Query.Each
378import Rel8.Query.Either
379import Rel8.Query.Evaluate
380import Rel8.Query.Exists
381import Rel8.Query.Filter
382import Rel8.Query.Indexed
383import Rel8.Query.Limit
384import Rel8.Query.List
385import Rel8.Query.Maybe
386import Rel8.Query.Null
387import Rel8.Query.Order
388import Rel8.Query.Rebind
389import Rel8.Query.SQL (showQuery)
390import Rel8.Query.Set
391import Rel8.Query.These
392import Rel8.Query.Values
393import Rel8.Query.Window
394import Rel8.Schema.Field
395import Rel8.Schema.HTable
396import Rel8.Schema.Name
397import Rel8.Schema.Null hiding ( nullable )
398import Rel8.Schema.Result ( Result )
399import Rel8.Schema.Table
400import Rel8.Statement.Delete
401import Rel8.Statement.Insert
402import Rel8.Statement.OnConflict
403import Rel8.Statement.Returning
404import Rel8.Statement.Select
405import Rel8.Statement.SQL
406import Rel8.Statement.Update
407import Rel8.Statement.View
408import Rel8.Table
409import Rel8.Table.ADT
410import Rel8.Table.Aggregate
411import Rel8.Table.Alternative
412import Rel8.Table.Bool
413import Rel8.Table.Either
414import Rel8.Table.Eq
415import Rel8.Table.HKD
416import Rel8.Table.List
417import Rel8.Table.Maybe
418import Rel8.Table.Name
419import Rel8.Table.NonEmpty
420import Rel8.Table.Null
421import Rel8.Table.Opaleye ( castTable )
422import Rel8.Table.Ord
423import Rel8.Table.Order
424import Rel8.Table.Projection
425import Rel8.Table.Rel8able ()
426import Rel8.Table.Serialize
427import Rel8.Table.These
428import Rel8.Table.Transpose
429import Rel8.Table.Window
430import Rel8.Type
431import Rel8.Type.Composite
432import Rel8.Type.Eq
433import Rel8.Type.Enum
434import Rel8.Type.Information
435import Rel8.Type.JSONBEncoded
436import Rel8.Type.JSONEncoded
437import Rel8.Type.Monoid
438import Rel8.Type.Num
439import Rel8.Type.Ord
440import Rel8.Type.ReadShow
441import Rel8.Type.Semigroup
442import Rel8.Type.String
443import Rel8.Type.Sum
444import Rel8.Window
447-- $running
448-- To run queries and otherwise interact with a PostgreSQL database, Rel8
449-- provides 'select', 'insert', 'update' and 'delete' functions. Note that
450-- 'insert', 'update' and 'delete' will generally need the
451-- `DuplicateRecordFields` language extension enabled.
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-- @
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.
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-- @
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--
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.