@@ -0,0 +1,1669 @@
1+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
2+{-# LANGUAGE
3+ AllowAmbiguousTypes
4+ , DataKinds
5+ , DeriveGeneric
6+ , DeriveDataTypeable
7+ , FlexibleContexts
8+ , FlexibleInstances
9+ , FunctionalDependencies
10+ , GADTs
11+ , GeneralizedNewtypeDeriving
12+ , LambdaCase
13+ , MagicHash
14+ , OverloadedStrings
15+ , PolyKinds
16+ , RankNTypes
17+ , ScopedTypeVariables
18+ , StandaloneDeriving
19+ , TypeApplications
20+ , TypeFamilies
21+ , TypeInType
22+ , TypeOperators
23+ , UndecidableInstances
24+#-}
25+
26+module Squeal.PostgreSQL.Statement
27+ ( -- * Column Expressions
28+ Expression (UnsafeExpression, renderExpression)
29+ , HasParameter (param)
30+ , HasColumn (getColumn)
31+ , Column (Column)
32+ , renderColumn
33+ -- ** Default
34+ , def
35+ , unDef
36+ -- ** Null
37+ , null_
38+ , unNull
39+ , coalesce
40+ , fromNull
41+ , isNull
42+ , isn'tNull
43+ , matchNull
44+ , nullIf
45+ -- ** Functions
46+ , unsafeBinaryOp
47+ , unsafeUnaryOp
48+ , unsafeFunction
49+ , atan2_
50+ , cast
51+ , div_
52+ , mod_
53+ , trunc
54+ , round_
55+ , ceiling_
56+ , greatest
57+ , least
58+ -- ** Conditions
59+ , Condition
60+ , true
61+ , false
62+ , not_
63+ , (&&*)
64+ , (||*)
65+ , caseWhenThenElse
66+ , ifThenElse
67+ , (==*)
68+ , (/=*)
69+ , (>=*)
70+ , (*<)
71+ , (*<=)
72+ , (>*)
73+ -- ** Time
74+ , currentDate
75+ , currentTime
76+ , currentTimestamp
77+ , localTime
78+ , localTimestamp
79+ -- ** Aggregation
80+ , unsafeAggregate
81+ , unsafeAggregateDistinct
82+ , sum_
83+ , sumDistinct
84+ , PGAvg (avg, avgDistinct)
85+ , bitAnd
86+ , bitOr
87+ , boolAnd
88+ , boolOr
89+ , countStar
90+ , countDistinctStar
91+ , count
92+ , every
93+ , jsonAgg
94+ , jsonbAgg
95+ , jsonObjectAgg
96+ , jsonbObjectAgg
97+ , max_
98+ , min_
99+ -- * Tables
100+ , Table (UnsafeTable, renderTable)
101+ , HasTable (getTable)
102+ -- ** Froms and Joins
103+ , FromClause (..)
104+ , renderFromClause
105+ -- ** Table Expressions
106+ , TableExpression (..)
107+ , renderTableExpression
108+ , from
109+ , where_
110+ , group
111+ , having
112+ , orderBy
113+ , limit
114+ , offset
115+ , By (By, By2)
116+ , renderBy
117+ , GroupByClause (NoGroups, Group)
118+ , renderGroupByClause
119+ , HavingClause (NoHaving, Having)
120+ , renderHavingClause
121+ , GroupedBy (getGroup1, getGroup2)
122+ , SortExpression (..)
123+ , renderSortExpression
124+ -- * TypeExpression
125+ , TypeExpression (UnsafeTypeExpression, renderTypeExpression)
126+ , PGTyped (pgtype)
127+ , bool
128+ , int2
129+ , smallint
130+ , int4
131+ , int
132+ , integer
133+ , int8
134+ , bigint
135+ , numeric
136+ , float4
137+ , real
138+ , float8
139+ , doublePrecision
140+ , serial2
141+ , smallserial
142+ , serial4
143+ , serial
144+ , serial8
145+ , bigserial
146+ , text
147+ , char
148+ , character
149+ , varchar
150+ , characterVarying
151+ , bytea
152+ , timestamp
153+ , timestampWithTimeZone
154+ , date
155+ , time
156+ , timeWithTimeZone
157+ , interval
158+ , uuid
159+ , json
160+ , jsonb
161+ , notNull
162+ , default_
163+ -- * Statements
164+ -- ** Queries
165+ , Query (UnsafeQuery, renderQuery)
166+ , select
167+ , selectDistinct
168+ , selectStar
169+ , selectDistinctStar
170+ , selectDotStar
171+ , selectDistinctDotStar
172+ , union
173+ , unionAll
174+ , intersect
175+ , intersectAll
176+ , except
177+ , exceptAll
178+ -- ** Data Definition Language
179+ , Definition (UnsafeDefinition, renderDefinition)
180+ , createTable
181+ , TableConstraint (UnsafeTableConstraint, renderTableConstraint)
182+ , check
183+ , unique
184+ , primaryKey
185+ , foreignKey
186+ , OnDelete (OnDeleteNoAction, OnDeleteRestrict, OnDeleteCascade)
187+ , renderOnDelete
188+ , OnUpdate (OnUpdateNoAction, OnUpdateRestrict, OnUpdateCascade)
189+ , renderOnUpdate
190+ , dropTable
191+ , alterTable
192+ , alterTableRename
193+ , AlterTable (UnsafeAlterTable, renderAlterTable)
194+ , addColumnDefault
195+ , addColumnNull
196+ , dropColumn
197+ , renameColumn
198+ , alterColumn
199+ , AlterColumn (UnsafeAlterColumn, renderAlterColumn)
200+ , setDefault
201+ , dropDefault
202+ , setNotNull
203+ , dropNotNull
204+ , alterType
205+ -- ** Data Manipulation Language
206+ , Manipulation (UnsafeManipulation, renderManipulation)
207+ , queryStatement
208+ , insertInto
209+ , ValuesClause (Values, ValuesQuery)
210+ , renderValuesClause
211+ , ReturningClause (ReturningStar, Returning)
212+ , renderReturningClause
213+ , ConflictClause (Conflict, OnConflictDoNothing, OnConflictDoUpdate)
214+ , renderConflictClause
215+ , UpdateExpression (Same, Set)
216+ , renderUpdateExpression
217+ , update
218+ , deleteFrom
219+ ) where
220+
221+import Control.Category
222+import Control.DeepSeq
223+import Data.ByteString (ByteString)
224+import Data.Data (Data)
225+import Data.Foldable
226+import Data.Maybe
227+import Data.Monoid hiding (All)
228+import Data.Ratio
229+import Data.String
230+import Data.Word
231+import Generics.SOP hiding (from)
232+import GHC.Exts
233+import GHC.OverloadedLabels
234+import GHC.TypeLits
235+import Prelude hiding (id, (.))
236+
237+import qualified GHC.Generics as GHC
238+import qualified Data.ByteString as ByteString
239+
240+import Squeal.PostgreSQL.Schema
241+
242+{-----------------------------------------
243+column expressions
244+-----------------------------------------}
245+
246+{- | `Expression`s are used in a variety of contexts,
247+such as in the target list of the `select` command,
248+as new column values in `insertInto` or `update`,
249+or in search `Condition`s in a number of commands.
250+
251+The expression syntax allows the calculation of
252+values from primitive expression using arithmetic, logical,
253+and other operations.
254+-}
255+newtype Expression
256+ (params :: [ColumnType])
257+ (tables :: TablesType)
258+ (grouping :: Grouping)
259+ (ty :: ColumnType)
260+ = UnsafeExpression { renderExpression :: ByteString }
261+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
262+
263+{- | A `HasParameter` constraint is used to indicate a value that is
264+supplied externally to a SQL statement.
265+`Squeal.PostgreSQL.PQ.manipulateParams`,
266+`Squeal.PostgreSQL.PQ.queryParams` and
267+`Squeal.PostgreSQL.PQ.traversePrepared` support specifying data values
268+separately from the SQL command string, in which case `param`s are used to
269+refer to the out-of-line data values.
270+-}
271+class (PGTyped (BaseType ty), KnownNat n)
272+ => HasParameter (n :: Nat) params ty | n params -> ty where
273+ param :: Expression params tables grouping ty
274+ param = UnsafeExpression $ parenthesized $
275+ "$" <> renderNat (Proxy @n) <+> "::"
276+ <+> renderTypeExpression (pgtype @(BaseType ty))
277+instance {-# OVERLAPPING #-} PGTyped (BaseType ty1)
278+ => HasParameter 1 (ty1:tys) ty1
279+instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) params ty)
280+ => HasParameter n (ty' : params) ty
281+
282+{- | A `HasColumn` constraint indicates an unqualified column reference.
283+`getColumn` can only be unambiguous when the `TableExpression` the column
284+references is unique, in which case the column may be referenced using
285+@-XOverloadedLabels@. Otherwise, combined with a `HasTable` constraint, the
286+qualified column reference operator `!` may be used.
287+-}
288+class KnownSymbol column => HasColumn column columns ty
289+ | column columns -> ty where
290+ getColumn
291+ :: HasUnique table tables columns
292+ => Alias column
293+ -> Expression params tables 'Ungrouped ty
294+ getColumn column = UnsafeExpression $ renderAlias column
295+instance {-# OVERLAPPING #-} KnownSymbol column
296+ => HasColumn column ((column ::: optionality ty) ': tys) ('Required ty)
297+instance {-# OVERLAPPABLE #-} (KnownSymbol column, HasColumn column table ty)
298+ => HasColumn column (ty' ': table) ty
299+
300+instance (HasColumn column columns ty, HasUnique table tables columns)
301+ => IsLabel column (Expression params tables 'Ungrouped ty) where
302+ fromLabel = getColumn (Alias @column)
303+
304+instance (HasTable table tables columns, HasColumn column columns ty)
305+ => IsTableColumn table column (Expression params tables 'Ungrouped ty) where
306+ table ! column = UnsafeExpression $
307+ renderAlias table <> "." <> renderAlias column
308+
309+data Column
310+ (columns :: ColumnsType)
311+ (columnty :: (Symbol,ColumnType))
312+ where
313+ Column
314+ :: HasColumn column columns ty
315+ => Alias column
316+ -> Column columns (column ::: ty)
317+deriving instance Show (Column columns columnty)
318+deriving instance Eq (Column columns columnty)
319+deriving instance Ord (Column columns columnty)
320+
321+-- | >>> renderExpression def
322+-- "DEFAULT"
323+def :: Expression params '[] 'Ungrouped ('Optional (nullity ty))
324+def = UnsafeExpression "DEFAULT"
325+
326+-- | >>> renderExpression $ unDef false
327+-- "FALSE"
328+unDef
329+ :: Expression params '[] 'Ungrouped ('Required (nullity ty))
330+ -> Expression params '[] 'Ungrouped ('Optional (nullity ty))
331+unDef = UnsafeExpression . renderExpression
332+
333+-- | analagous to `Nothing`
334+--
335+-- >>> renderExpression $ null_
336+-- "NULL"
337+null_ :: Expression params tables grouping (optionality ('Null ty))
338+null_ = UnsafeExpression "NULL"
339+
340+-- | analagous to `Just`
341+--
342+-- >>> renderExpression $ unNull true
343+-- "TRUE"
344+unNull
345+ :: Expression params tables grouping (optionality ('NotNull ty))
346+ -> Expression params tables grouping (optionality ('Null ty))
347+unNull = UnsafeExpression . renderExpression
348+
349+-- | return the leftmost value which is not NULL
350+--
351+-- >>> renderExpression $ coalesce [null_, unNull true] false
352+-- "COALESCE(NULL, TRUE, FALSE)"
353+coalesce
354+ :: [Expression params tables grouping ('Required ('Null ty))]
355+ -> Expression params tables grouping ('Required ('NotNull ty))
356+ -> Expression params tables grouping ('Required ('NotNull ty))
357+coalesce nullxs notNullx = UnsafeExpression $
358+ "COALESCE" <> parenthesized (commaSeparated
359+ ((renderExpression <$> nullxs) <> [renderExpression notNullx]))
360+
361+-- | analagous to `fromMaybe` using @COALESCE@
362+--
363+-- >>> renderExpression $ fromNull true null_
364+-- "COALESCE(NULL, TRUE)"
365+fromNull
366+ :: Expression params tables grouping ('Required ('NotNull ty))
367+ -> Expression params tables grouping ('Required ('Null ty))
368+ -> Expression params tables grouping ('Required ('NotNull ty))
369+fromNull notNullx nullx = coalesce [nullx] notNullx
370+
371+-- | >>> renderExpression $ null_ & isNull
372+-- "NULL IS NULL"
373+isNull
374+ :: Expression params tables grouping ('Required ('Null ty))
375+ -> Condition params tables grouping
376+isNull x = UnsafeExpression $ renderExpression x <+> "IS NULL"
377+
378+-- | >>> renderExpression $ null_ & isn'tNull
379+-- "NULL IS NOT NULL"
380+isn'tNull
381+ :: Expression params tables grouping ('Required ('Null ty))
382+ -> Condition params tables grouping
383+isn'tNull x = UnsafeExpression $ renderExpression x <+> "IS NOT NULL"
384+
385+-- | analagous to `maybe` using @IS NULL@
386+--
387+-- >>> renderExpression $ matchNull true not null_
388+-- "CASE WHEN NULL IS NULL THEN TRUE ELSE (NOT TRUE) END"
389+matchNull
390+ :: Expression params tables grouping ('Required nullty)
391+ -> ( Expression params tables grouping ('Required ('NotNull ty))
392+ -> Expression params tables grouping ('Required nullty) )
393+ -> Expression params tables grouping ('Required ('Null ty))
394+ -> Expression params tables grouping ('Required nullty)
395+matchNull y f x = ifThenElse (isNull x) y
396+ (f (UnsafeExpression (renderExpression y)))
397+
398+-- | left inverse to `fromNull`
399+--
400+-- >>> renderExpression $ nullIf false (fromNull false null_)
401+-- "NULL IF (FALSE, COALESCE(NULL, FALSE))"
402+nullIf
403+ :: Expression params tables grouping ('Required ('NotNull ty))
404+ -> Expression params tables grouping ('Required ('NotNull ty))
405+ -> Expression params tables grouping ('Required ('Null ty))
406+nullIf x y = UnsafeExpression $ "NULL IF" <+> parenthesized
407+ (renderExpression x <> ", " <> renderExpression y)
408+
409+-- | >>> renderExpression $ greatest @'[_] currentTimestamp [param (Proxy @1)]
410+-- "GREATEST(CURRENT_TIMESTAMP, ($1 :: timestamp with time zone))"
411+greatest
412+ :: Expression params tables grouping ('Required nullty)
413+ -> [Expression params tables grouping ('Required nullty)]
414+ -> Expression params table grouping ('Required nullty)
415+greatest x xs = UnsafeExpression $ "GREATEST("
416+ <> commaSeparated (renderExpression <$> (x:xs)) <> ")"
417+
418+-- | >>> renderExpression $ least currentTimestamp [null_]
419+-- "LEAST(CURRENT_TIMESTAMP, NULL)"
420+least
421+ :: Expression params tables grouping ('Required nullty)
422+ -> [Expression params tables grouping ('Required nullty)]
423+ -> Expression params table grouping ('Required nullty)
424+least x xs = UnsafeExpression $ "LEAST("
425+ <> commaSeparated (renderExpression <$> (x:xs)) <> ")"
426+
427+unsafeBinaryOp
428+ :: ByteString
429+ -> Expression params tables grouping ('Required ty0)
430+ -> Expression params tables grouping ('Required ty1)
431+ -> Expression params tables grouping ('Required ty2)
432+unsafeBinaryOp op x y = UnsafeExpression $ parenthesized $
433+ renderExpression x <+> op <+> renderExpression y
434+
435+unsafeUnaryOp
436+ :: ByteString
437+ -> Expression params tables grouping ('Required ty0)
438+ -> Expression params tables grouping ('Required ty1)
439+unsafeUnaryOp op x = UnsafeExpression $ parenthesized $
440+ op <+> renderExpression x
441+
442+unsafeFunction
443+ :: ByteString
444+ -> Expression params tables grouping ('Required xty)
445+ -> Expression params tables grouping ('Required yty)
446+unsafeFunction fun x = UnsafeExpression $
447+ fun <> parenthesized (renderExpression x)
448+
449+instance PGNum ty
450+ => Num (Expression params tables grouping ('Required (nullity ty))) where
451+ (+) = unsafeBinaryOp "+"
452+ (-) = unsafeBinaryOp "-"
453+ (*) = unsafeBinaryOp "*"
454+ abs = unsafeFunction "abs"
455+ signum = unsafeFunction "sign"
456+ fromInteger
457+ = UnsafeExpression
458+ . fromString
459+ . show
460+
461+instance (PGNum ty, PGFloating ty) => Fractional
462+ (Expression params tables grouping ('Required (nullity ty))) where
463+ (/) = unsafeBinaryOp "/"
464+ fromRational x = fromInteger (numerator x) / fromInteger (denominator x)
465+
466+instance (PGNum ty, PGFloating ty) => Floating
467+ (Expression params tables grouping ('Required (nullity ty))) where
468+ pi = UnsafeExpression "pi()"
469+ exp = unsafeFunction "exp"
470+ log = unsafeFunction "ln"
471+ sqrt = unsafeFunction "sqrt"
472+ b ** x = UnsafeExpression $
473+ "power(" <> renderExpression b <> ", " <> renderExpression x <> ")"
474+ logBase b y = log y / log b
475+ sin = unsafeFunction "sin"
476+ cos = unsafeFunction "cos"
477+ tan = unsafeFunction "tan"
478+ asin = unsafeFunction "asin"
479+ acos = unsafeFunction "acos"
480+ atan = unsafeFunction "atan"
481+ sinh x = (exp x - exp (-x)) / 2
482+ cosh x = (exp x + exp (-x)) / 2
483+ tanh x = sinh x / cosh x
484+ asinh x = log (x + sqrt (x*x + 1))
485+ acosh x = log (x + sqrt (x*x - 1))
486+ atanh x = log ((1 + x) / (1 - x)) / 2
487+
488+atan2_
489+ :: PGFloating float
490+ => Expression params tables grouping ('Required (nullity float))
491+ -> Expression params tables grouping ('Required (nullity float))
492+ -> Expression params tables grouping ('Required (nullity float))
493+atan2_ y x = UnsafeExpression $
494+ "atan2(" <> renderExpression y <> ", " <> renderExpression x <> ")"
495+
496+cast
497+ :: TypeExpression ('Required ('Null ty1))
498+ -> Expression params tables grouping ('Required (nullity ty0))
499+ -> Expression params tables grouping ('Required (nullity ty1))
500+cast ty x = UnsafeExpression $ parenthesized $
501+ renderExpression x <+> "::" <+> renderTypeExpression ty
502+
503+div_
504+ :: PGIntegral int
505+ => Expression params tables grouping ('Required (nullity int))
506+ -> Expression params tables grouping ('Required (nullity int))
507+ -> Expression params tables grouping ('Required (nullity int))
508+div_ = unsafeBinaryOp "/"
509+
510+mod_
511+ :: PGIntegral int
512+ => Expression params tables grouping ('Required (nullity int))
513+ -> Expression params tables grouping ('Required (nullity int))
514+ -> Expression params tables grouping ('Required (nullity int))
515+mod_ = unsafeBinaryOp "%"
516+
517+trunc
518+ :: PGFloating frac
519+ => Expression params tables grouping ('Required (nullity frac))
520+ -> Expression params tables grouping ('Required (nullity frac))
521+trunc = unsafeFunction "trunc"
522+
523+round_
524+ :: PGFloating frac
525+ => Expression params tables grouping ('Required (nullity frac))
526+ -> Expression params tables grouping ('Required (nullity frac))
527+round_ = unsafeFunction "round"
528+
529+ceiling_
530+ :: PGFloating frac
531+ => Expression params tables grouping ('Required (nullity frac))
532+ -> Expression params tables grouping ('Required (nullity frac))
533+ceiling_ = unsafeFunction "ceiling"
534+
535+instance IsString
536+ (Expression params tables grouping ('Required (nullity 'PGtext))) where
537+ fromString str = UnsafeExpression $
538+ "E\'" <> fromString (escape =<< str) <> "\'"
539+ where
540+ escape = \case
541+ '\NUL' -> "\\0"
542+ '\'' -> "''"
543+ '"' -> "\\\""
544+ '\b' -> "\\b"
545+ '\n' -> "\\n"
546+ '\r' -> "\\r"
547+ '\t' -> "\\t"
548+ '\\' -> "\\\\"
549+ c -> [c]
550+
551+type Condition params tables grouping =
552+ Expression params tables grouping ('Required ('NotNull 'PGbool))
553+
554+true :: Condition params tables grouping
555+true = UnsafeExpression "TRUE"
556+
557+false :: Condition params tables grouping
558+false = UnsafeExpression "FALSE"
559+
560+not_
561+ :: Condition params tables grouping
562+ -> Condition params tables grouping
563+not_ = unsafeUnaryOp "NOT"
564+
565+(&&*)
566+ :: Condition params tables grouping
567+ -> Condition params tables grouping
568+ -> Condition params tables grouping
569+(&&*) = unsafeBinaryOp "AND"
570+
571+(||*)
572+ :: Condition params tables grouping
573+ -> Condition params tables grouping
574+ -> Condition params tables grouping
575+(||*) = unsafeBinaryOp "OR"
576+
577+caseWhenThenElse
578+ :: [ ( Condition params tables grouping
579+ , Expression params tables grouping ('Required ty)
580+ ) ]
581+ -> Expression params tables grouping ('Required ty)
582+ -> Expression params tables grouping ('Required ty)
583+caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
584+ [ "CASE"
585+ , mconcat
586+ [ mconcat
587+ [ " WHEN ", renderExpression when_
588+ , " THEN ", renderExpression then_
589+ ]
590+ | (when_,then_) <- whenThens
591+ ]
592+ , " ELSE ", renderExpression else_
593+ , " END"
594+ ]
595+
596+ifThenElse
597+ :: Condition params tables grouping
598+ -> Expression params tables grouping ('Required ty)
599+ -> Expression params tables grouping ('Required ty)
600+ -> Expression params tables grouping ('Required ty)
601+ifThenElse if_ then_ else_ = caseWhenThenElse [(if_,then_)] else_
602+
603+(==*)
604+ :: Expression params tables grouping ('Required (nullity ty))
605+ -> Expression params tables grouping ('Required (nullity ty))
606+ -> Expression params tables grouping ('Required (nullity 'PGbool))
607+(==*) = unsafeBinaryOp "="
608+infix 4 ==*
609+
610+(/=*)
611+ :: Expression params tables grouping ('Required (nullity ty))
612+ -> Expression params tables grouping ('Required (nullity ty))
613+ -> Expression params tables grouping ('Required (nullity 'PGbool))
614+(/=*) = unsafeBinaryOp "<>"
615+infix 4 /=*
616+
617+(>=*)
618+ :: Expression params tables grouping ('Required (nullity ty))
619+ -> Expression params tables grouping ('Required (nullity ty))
620+ -> Expression params tables grouping ('Required (nullity 'PGbool))
621+(>=*) = unsafeBinaryOp ">="
622+infix 4 >=*
623+
624+(*<)
625+ :: Expression params tables grouping ('Required (nullity ty))
626+ -> Expression params tables grouping ('Required (nullity ty))
627+ -> Expression params tables grouping ('Required (nullity 'PGbool))
628+(*<) = unsafeBinaryOp "<"
629+infix 4 *<
630+
631+(*<=)
632+ :: Expression params tables grouping ('Required (nullity ty))
633+ -> Expression params tables grouping ('Required (nullity ty))
634+ -> Expression params tables grouping ('Required (nullity 'PGbool))
635+(*<=) = unsafeBinaryOp "<="
636+infix 4 *<=
637+
638+(>*)
639+ :: Expression params tables grouping ('Required (nullity ty))
640+ -> Expression params tables grouping ('Required (nullity ty))
641+ -> Expression params tables grouping ('Required (nullity 'PGbool))
642+(>*) = unsafeBinaryOp ">"
643+infix 4 >*
644+
645+currentDate
646+ :: Expression params tables grouping ('Required (nullity 'PGdate))
647+currentDate = UnsafeExpression "CURRENT_DATE"
648+
649+currentTime
650+ :: Expression params tables grouping ('Required (nullity 'PGtimetz))
651+currentTime = UnsafeExpression "CURRENT_TIME"
652+
653+currentTimestamp
654+ :: Expression params tables grouping ('Required (nullity 'PGtimestamptz))
655+currentTimestamp = UnsafeExpression "CURRENT_TIMESTAMP"
656+
657+localTime
658+ :: Expression params tables grouping ('Required (nullity 'PGtime))
659+localTime = UnsafeExpression "LOCALTIME"
660+
661+localTimestamp
662+ :: Expression params tables grouping ('Required (nullity 'PGtimestamp))
663+localTimestamp = UnsafeExpression "LOCALTIMESTAMP"
664+
665+data SortExpression params tables grouping where
666+ Asc
667+ :: Expression params tables grouping ('Required ('NotNull ty))
668+ -> SortExpression params tables grouping
669+ Desc
670+ :: Expression params tables grouping ('Required ('NotNull ty))
671+ -> SortExpression params tables grouping
672+ AscNullsFirst
673+ :: Expression params tables grouping ('Required ('Null ty))
674+ -> SortExpression params tables grouping
675+ AscNullsLast
676+ :: Expression params tables grouping ('Required ('Null ty))
677+ -> SortExpression params tables grouping
678+ DescNullsFirst
679+ :: Expression params tables grouping ('Required ('Null ty))
680+ -> SortExpression params tables grouping
681+ DescNullsLast
682+ :: Expression params tables grouping ('Required ('Null ty))
683+ -> SortExpression params tables grouping
684+deriving instance Show (SortExpression params tables grouping)
685+
686+renderSortExpression :: SortExpression params tables grouping -> ByteString
687+renderSortExpression = \case
688+ Asc expression -> renderExpression expression <+> "ASC"
689+ Desc expression -> renderExpression expression <+> "DESC"
690+ AscNullsFirst expression -> renderExpression expression
691+ <+> "ASC NULLS FIRST"
692+ DescNullsFirst expression -> renderExpression expression
693+ <+> "DESC NULLS FIRST"
694+ AscNullsLast expression -> renderExpression expression <+> "ASC NULLS LAST"
695+ DescNullsLast expression -> renderExpression expression <+> "DESC NULLS LAST"
696+
697+
698+{-----------------------------------------
699+aggregation
700+-----------------------------------------}
701+
702+data By
703+ (tables :: TablesType)
704+ (by :: (Symbol,Symbol)) where
705+ By
706+ :: (tables ~ '[table ::: columns], HasColumn column columns ty)
707+ => Alias column
708+ -> By tables '(table, column)
709+ By2
710+ :: (HasTable table tables columns, HasColumn column columns ty)
711+ => (Alias table, Alias column)
712+ -> By tables '(table, column)
713+deriving instance Show (By tables by)
714+deriving instance Eq (By tables by)
715+deriving instance Ord (By tables by)
716+
717+renderBy :: By tables tabcolty -> ByteString
718+renderBy = \case
719+ By column -> renderAlias column
720+ By2 (table, column) -> renderAlias table <> "." <> renderAlias column
721+
722+data GroupByClause tables grouping where
723+ NoGroups :: GroupByClause tables 'Ungrouped
724+ Group
725+ :: SListI bys
726+ => NP (By tables) bys
727+ -> GroupByClause tables ('Grouped bys)
728+
729+renderGroupByClause :: GroupByClause tables grouping -> ByteString
730+renderGroupByClause = \case
731+ NoGroups -> ""
732+ Group Nil -> ""
733+ Group bys -> " GROUP BY" <+> renderCommaSeparated renderBy bys
734+
735+data HavingClause params tables grouping where
736+ NoHaving :: HavingClause params tables 'Ungrouped
737+ Having
738+ :: [Condition params tables ('Grouped bys)]
739+ -> HavingClause params tables ('Grouped bys)
740+deriving instance Show (HavingClause params tables grouping)
741+deriving instance Eq (HavingClause params tables grouping)
742+deriving instance Ord (HavingClause params tables grouping)
743+
744+renderHavingClause :: HavingClause params tables grouping -> ByteString
745+renderHavingClause = \case
746+ NoHaving -> ""
747+ Having [] -> ""
748+ Having conditions ->
749+ " HAVING" <+> commaSeparated (renderExpression <$> conditions)
750+
751+class (KnownSymbol table, KnownSymbol column)
752+ => GroupedBy table column bys where
753+ getGroup1
754+ :: (tables ~ '[table ::: columns], HasColumn column columns ty)
755+ => Alias column
756+ -> Expression params tables ('Grouped bys) ty
757+ getGroup1 column = UnsafeExpression $ renderAlias column
758+ getGroup2
759+ :: (HasTable table tables columns, HasColumn column columns ty)
760+ => Alias table
761+ -> Alias column
762+ -> Expression params tables ('Grouped bys) ty
763+ getGroup2 table column = UnsafeExpression $
764+ renderAlias table <> "." <> renderAlias column
765+instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
766+ => GroupedBy table column ('(table,column) ': bys)
767+instance {-# OVERLAPPABLE #-}
768+ ( KnownSymbol table
769+ , KnownSymbol column
770+ , GroupedBy table column bys
771+ ) => GroupedBy table column (tabcol ': bys)
772+
773+instance
774+ ( HasUnique table tables columns
775+ , HasColumn column columns ty
776+ , GroupedBy table column bys
777+ ) => IsLabel column
778+ (Expression params tables ('Grouped bys) ty) where
779+ fromLabel = getGroup1 (Alias @column)
780+
781+instance
782+ ( HasTable table tables columns
783+ , HasColumn column columns ty
784+ , GroupedBy table column bys
785+ ) => IsTableColumn table column
786+ (Expression params tables ('Grouped bys) ty) where (!) = getGroup2
787+
788+-- data AllOrDistinct = All | Distinct
789+-- deriving (GHC.Generic,Show,Eq,Ord,Data)
790+-- instance NFData AllOrDistinct
791+
792+-- renderAllOrDistinct :: AllOrDistinct -> ByteString
793+-- renderAllOrDistinct = \case
794+-- All -> "ALL"
795+-- Distinct -> "DISTINCT"
796+
797+unsafeAggregate, unsafeAggregateDistinct
798+ :: ByteString
799+ -> Expression params tables 'Ungrouped ('Required xty)
800+ -> Expression params tables ('Grouped bys) ('Required yty)
801+unsafeAggregate fun x = UnsafeExpression $ mconcat
802+ [fun, "(", renderExpression x, ")"]
803+unsafeAggregateDistinct fun x = UnsafeExpression $ mconcat
804+ [fun, "(DISTINCT ", renderExpression x, ")"]
805+
806+sum_, sumDistinct
807+ :: PGNum ty
808+ => Expression params tables 'Ungrouped ('Required (nullity ty))
809+ -> Expression params tables ('Grouped bys) ('Required (nullity ty))
810+sum_ = unsafeAggregate "sum"
811+sumDistinct = unsafeAggregateDistinct "sum"
812+
813+class PGAvg ty avg | ty -> avg where
814+ avg, avgDistinct
815+ :: Expression params tables 'Ungrouped ('Required (nullity ty))
816+ -> Expression params tables ('Grouped bys) ('Required (nullity avg))
817+ avg = unsafeAggregate "avg"
818+ avgDistinct = unsafeAggregateDistinct "avg"
819+instance PGAvg 'PGint2 'PGnumeric
820+instance PGAvg 'PGint4 'PGnumeric
821+instance PGAvg 'PGint8 'PGnumeric
822+instance PGAvg 'PGnumeric 'PGnumeric
823+instance PGAvg 'PGfloat4 'PGfloat8
824+instance PGAvg 'PGfloat8 'PGfloat8
825+instance PGAvg 'PGinterval 'PGinterval
826+
827+bitAnd, bitOr
828+ :: PGIntegral int
829+ => Expression params tables 'Ungrouped ('Required (nullity int))
830+ -> Expression params tables ('Grouped bys) ('Required (nullity int))
831+bitAnd = unsafeAggregate "bit_and"
832+bitOr = unsafeAggregate "bit_or"
833+
834+boolAnd, boolOr
835+ :: Expression params tables 'Ungrouped ('Required (nullity 'PGbool))
836+ -> Expression params tables ('Grouped bys) ('Required (nullity 'PGbool))
837+boolAnd = unsafeAggregate "bool_and"
838+boolOr = unsafeAggregate "bool_or"
839+
840+countStar, countDistinctStar
841+ :: Expression params table ('Grouped bys) ('Required ('NotNull 'PGint8))
842+countStar = UnsafeExpression $ "count(*)"
843+countDistinctStar = UnsafeExpression $ "count(DISTINCT *)"
844+
845+count
846+ :: Expression params tables 'Ungrouped ('Required ty)
847+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGint8))
848+count = unsafeAggregate "count"
849+
850+every
851+ :: Expression params tables 'Ungrouped ('Required (nullity 'PGbool))
852+ -> Expression params tables ('Grouped bys) ('Required (nullity 'PGbool))
853+every = unsafeAggregate "every"
854+
855+jsonAgg
856+ :: Expression params tables 'Ungrouped ('Required ty)
857+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjson))
858+jsonAgg = unsafeAggregate "json_agg"
859+
860+jsonbAgg
861+ :: Expression params tables 'Ungrouped ('Required ty)
862+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjsonb))
863+jsonbAgg = unsafeAggregate "jsonb_agg"
864+
865+jsonObjectAgg
866+ :: Expression params tables 'Ungrouped ('Required ('NotNull keyty))
867+ -> Expression params tables 'Ungrouped ('Required valty)
868+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjson))
869+jsonObjectAgg k v = UnsafeExpression $
870+ "json_object_agg(" <> renderExpression k
871+ <> ", " <> renderExpression v <> ")"
872+
873+jsonbObjectAgg
874+ :: Expression params tables 'Ungrouped ('Required ('NotNull keyty))
875+ -> Expression params tables 'Ungrouped ('Required valty)
876+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjsonb))
877+jsonbObjectAgg k v = UnsafeExpression $
878+ "jsonb_object_agg("
879+ <> renderExpression k <> ", " <> renderExpression v <> ")"
880+
881+max_, min_
882+ :: Expression params tables 'Ungrouped ('Required (nullity ty))
883+ -> Expression params tables ('Grouped bys) ('Required (nullity ty))
884+max_ = unsafeAggregate "max"
885+min_ = unsafeAggregate "min"
886+
887+{-----------------------------------------
888+tables
889+-----------------------------------------}
890+
891+newtype Table
892+ (schema :: TablesType)
893+ (columns :: ColumnsType)
894+ = UnsafeTable { renderTable :: ByteString }
895+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
896+
897+class KnownSymbol table => HasTable table tables columns
898+ | table tables -> columns where
899+ getTable :: Alias table -> Table tables columns
900+ getTable table = UnsafeTable $ renderAlias table
901+instance {-# OVERLAPPING #-} KnownSymbol table
902+ => HasTable table ((table ::: columns) ': tables) columns
903+instance {-# OVERLAPPABLE #-}
904+ (KnownSymbol table, HasTable table schema columns)
905+ => HasTable table (table' ': schema) columns
906+
907+instance HasTable table schema columns
908+ => IsLabel table (Table schema columns) where
909+ fromLabel = getTable (Alias @table)
910+
911+data FromClause params schema tables where
912+ Table
913+ :: Aliased (Table schema) table
914+ -> FromClause params schema '[table]
915+ Subquery
916+ :: Aliased (Query schema params) table
917+ -> FromClause params schema '[table]
918+ CrossJoin
919+ :: FromClause params schema right
920+ -> FromClause params schema left
921+ -> FromClause params schema (Join left right)
922+ InnerJoin
923+ :: FromClause params schema right
924+ -> Condition params (Join left right) 'Ungrouped
925+ -> FromClause params schema left
926+ -> FromClause params schema (Join left right)
927+ LeftOuterJoin
928+ :: FromClause params schema right
929+ -> Condition params (Join left right) 'Ungrouped
930+ -> FromClause params schema left
931+ -> FromClause params schema (Join left (NullifyTables right))
932+ RightOuterJoin
933+ :: FromClause params schema right
934+ -> Condition params (Join left right) 'Ungrouped
935+ -> FromClause params schema left
936+ -> FromClause params schema (Join (NullifyTables left) right)
937+ FullOuterJoin
938+ :: FromClause params schema right
939+ -> Condition params (Join left right) 'Ungrouped
940+ -> FromClause params schema left
941+ -> FromClause params schema
942+ (Join (NullifyTables left) (NullifyTables right))
943+
944+renderFromClause :: FromClause params schema tables -> ByteString
945+renderFromClause = \case
946+ Table table -> renderAliased renderTable table
947+ Subquery selection -> renderAliased (parenthesized . renderQuery) selection
948+ CrossJoin right left ->
949+ renderFromClause left <+> "CROSS JOIN" <+> renderFromClause right
950+ InnerJoin right on left -> renderJoin "INNER JOIN" right on left
951+ LeftOuterJoin right on left -> renderJoin "LEFT OUTER JOIN" right on left
952+ RightOuterJoin right on left -> renderJoin "RIGHT OUTER JOIN" right on left
953+ FullOuterJoin right on left -> renderJoin "FULL OUTER JOIN" right on left
954+ where
955+ renderJoin op right on left =
956+ renderFromClause left <+> op <+> renderFromClause right
957+ <+> "ON" <+> renderExpression on
958+
959+data TableExpression
960+ (params :: [ColumnType])
961+ (schema :: TablesType)
962+ (tables :: TablesType)
963+ (grouping :: Grouping)
964+ = TableExpression
965+ { fromClause :: FromClause params schema tables
966+ , whereClause :: [Condition params tables 'Ungrouped]
967+ , groupByClause :: GroupByClause tables grouping
968+ , havingClause :: HavingClause params tables grouping
969+ , orderByClause :: [SortExpression params tables grouping]
970+ , limitClause :: [Word64]
971+ , offsetClause :: [Word64]
972+ }
973+
974+renderTableExpression
975+ :: TableExpression params schema tables grouping
976+ -> ByteString
977+renderTableExpression
978+ (TableExpression tables whs' grps' hvs' srts' lims' offs') = mconcat
979+ [ "FROM ", renderFromClause tables
980+ , renderWheres whs'
981+ , renderGroupByClause grps'
982+ , renderHavingClause hvs'
983+ , renderSorts srts'
984+ , renderLimits lims'
985+ , renderOffsets offs'
986+ ]
987+ where
988+ renderWheres = \case
989+ [] -> ""
990+ wh:[] -> " WHERE" <+> renderExpression wh
991+ wh:whs -> " WHERE" <+> renderExpression (foldr (&&*) wh whs)
992+ renderSorts = \case
993+ [] -> ""
994+ srts -> " SORT BY"
995+ <+> commaSeparated (renderSortExpression <$> srts)
996+ renderLimits = \case
997+ [] -> ""
998+ lims -> " LIMIT" <+> fromString (show (minimum lims))
999+ renderOffsets = \case
1000+ [] -> ""
1001+ offs -> " OFFSET" <+> fromString (show (sum offs))
1002+
1003+from
1004+ :: FromClause params schema tables
1005+ -> TableExpression params schema tables 'Ungrouped
1006+from tables = TableExpression tables [] NoGroups NoHaving [] [] []
1007+
1008+where_
1009+ :: Condition params tables 'Ungrouped
1010+ -> TableExpression params schema tables grouping
1011+ -> TableExpression params schema tables grouping
1012+where_ wh tables = tables {whereClause = wh : whereClause tables}
1013+
1014+group
1015+ :: SListI bys
1016+ => NP (By tables) bys
1017+ -> TableExpression params schema tables 'Ungrouped
1018+ -> TableExpression params schema tables ('Grouped bys)
1019+group bys tables = TableExpression
1020+ { fromClause = fromClause tables
1021+ , whereClause = whereClause tables
1022+ , groupByClause = Group bys
1023+ , havingClause = Having []
1024+ , orderByClause = []
1025+ , limitClause = limitClause tables
1026+ , offsetClause = offsetClause tables
1027+ }
1028+
1029+having
1030+ :: Condition params tables ('Grouped bys)
1031+ -> TableExpression params schema tables ('Grouped bys)
1032+ -> TableExpression params schema tables ('Grouped bys)
1033+having hv tables = tables
1034+ { havingClause = case havingClause tables of Having hvs -> Having (hv:hvs) }
1035+
1036+orderBy
1037+ :: [SortExpression params tables grouping]
1038+ -> TableExpression params schema tables grouping
1039+ -> TableExpression params schema tables grouping
1040+orderBy srts tables = tables {orderByClause = orderByClause tables ++ srts}
1041+
1042+limit
1043+ :: Word64
1044+ -> TableExpression params schema tables grouping
1045+ -> TableExpression params schema tables grouping
1046+limit lim tables = tables {limitClause = lim : limitClause tables}
1047+
1048+offset
1049+ :: Word64
1050+ -> TableExpression params schema tables grouping
1051+ -> TableExpression params schema tables grouping
1052+offset off tables = tables {offsetClause = off : offsetClause tables}
1053+
1054+{-----------------------------------------
1055+type expressions
1056+-----------------------------------------}
1057+
1058+newtype TypeExpression (ty :: ColumnType)
1059+ = UnsafeTypeExpression { renderTypeExpression :: ByteString }
1060+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1061+
1062+bool :: TypeExpression ('Required ('Null 'PGbool))
1063+bool = UnsafeTypeExpression "bool"
1064+int2 :: TypeExpression ('Required ('Null 'PGint2))
1065+int2 = UnsafeTypeExpression "int2"
1066+smallint :: TypeExpression ('Required ('Null 'PGint2))
1067+smallint = UnsafeTypeExpression "smallint"
1068+int4 :: TypeExpression ('Required ('Null 'PGint4))
1069+int4 = UnsafeTypeExpression "int4"
1070+int :: TypeExpression ('Required ('Null 'PGint4))
1071+int = UnsafeTypeExpression "int"
1072+integer :: TypeExpression ('Required ('Null 'PGint4))
1073+integer = UnsafeTypeExpression "integer"
1074+int8 :: TypeExpression ('Required ('Null 'PGint8))
1075+int8 = UnsafeTypeExpression "int8"
1076+bigint :: TypeExpression ('Required ('Null 'PGint8))
1077+bigint = UnsafeTypeExpression "bigint"
1078+numeric :: TypeExpression ('Required ('Null 'PGnumeric))
1079+numeric = UnsafeTypeExpression "numeric"
1080+float4 :: TypeExpression ('Required ('Null 'PGfloat4))
1081+float4 = UnsafeTypeExpression "float4"
1082+real :: TypeExpression ('Required ('Null 'PGfloat4))
1083+real = UnsafeTypeExpression "real"
1084+float8 :: TypeExpression ('Required ('Null 'PGfloat8))
1085+float8 = UnsafeTypeExpression "float8"
1086+doublePrecision :: TypeExpression ('Required ('Null 'PGfloat8))
1087+doublePrecision = UnsafeTypeExpression "double precision"
1088+serial2 :: TypeExpression ('Optional ('NotNull 'PGint2))
1089+serial2 = UnsafeTypeExpression "serial2"
1090+smallserial :: TypeExpression ('Optional ('NotNull 'PGint2))
1091+smallserial = UnsafeTypeExpression "smallserial"
1092+serial4 :: TypeExpression ('Optional ('NotNull 'PGint4))
1093+serial4 = UnsafeTypeExpression "serial4"
1094+serial :: TypeExpression ('Optional ('NotNull 'PGint4))
1095+serial = UnsafeTypeExpression "serial"
1096+serial8 :: TypeExpression ('Optional ('NotNull 'PGint8))
1097+serial8 = UnsafeTypeExpression "serial8"
1098+bigserial :: TypeExpression ('Optional ('NotNull 'PGint8))
1099+bigserial = UnsafeTypeExpression "bigserial"
1100+text :: TypeExpression ('Required ('Null 'PGtext))
1101+text = UnsafeTypeExpression "text"
1102+char
1103+ :: (KnownNat n, 1 <= n)
1104+ => proxy n
1105+ -> TypeExpression ('Required ('Null ('PGchar n)))
1106+char p = UnsafeTypeExpression $ "char(" <> renderNat p <> ")"
1107+character
1108+ :: (KnownNat n, 1 <= n)
1109+ => proxy n
1110+ -> TypeExpression ('Required ('Null ('PGchar n)))
1111+character p = UnsafeTypeExpression $ "character(" <> renderNat p <> ")"
1112+varchar
1113+ :: (KnownNat n, 1 <= n)
1114+ => proxy n
1115+ -> TypeExpression ('Required ('Null ('PGvarchar n)))
1116+varchar p = UnsafeTypeExpression $ "varchar(" <> renderNat p <> ")"
1117+characterVarying
1118+ :: (KnownNat n, 1 <= n)
1119+ => proxy n
1120+ -> TypeExpression ('Required ('Null ('PGvarchar n)))
1121+characterVarying p = UnsafeTypeExpression $
1122+ "character varying(" <> renderNat p <> ")"
1123+bytea :: TypeExpression ('Required ('Null ('PGbytea)))
1124+bytea = UnsafeTypeExpression "bytea"
1125+timestamp :: TypeExpression ('Required ('Null ('PGtimestamp)))
1126+timestamp = UnsafeTypeExpression "timestamp"
1127+timestampWithTimeZone :: TypeExpression ('Required ('Null ('PGtimestamptz)))
1128+timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
1129+date :: TypeExpression ('Required ('Null ('PGdate)))
1130+date = UnsafeTypeExpression "date"
1131+time :: TypeExpression ('Required ('Null ('PGtime)))
1132+time = UnsafeTypeExpression "time"
1133+timeWithTimeZone :: TypeExpression ('Required ('Null ('PGtimetz)))
1134+timeWithTimeZone = UnsafeTypeExpression "time with time zone"
1135+interval :: TypeExpression ('Required ('Null ('PGinterval)))
1136+interval = UnsafeTypeExpression "interval"
1137+uuid :: TypeExpression ('Required ('Null ('PGuuid)))
1138+uuid = UnsafeTypeExpression "uuid"
1139+json :: TypeExpression ('Required ('Null ('PGjson)))
1140+json = UnsafeTypeExpression "json"
1141+jsonb :: TypeExpression ('Required ('Null ('PGjsonb)))
1142+jsonb = UnsafeTypeExpression "jsonb"
1143+
1144+notNull
1145+ :: TypeExpression (optionality ('Null ty))
1146+ -> TypeExpression (optionality ('NotNull ty))
1147+notNull ty = UnsafeTypeExpression $ renderTypeExpression ty <+> "NOT NULL"
1148+default_
1149+ :: Expression '[] '[] 'Ungrouped ('Required ty)
1150+ -> TypeExpression ('Required ty)
1151+ -> TypeExpression ('Optional ty)
1152+default_ x ty = UnsafeTypeExpression $
1153+ renderTypeExpression ty <+> "DEFAULT" <+> renderExpression x
1154+
1155+class PGTyped (ty :: PGType) where
1156+ pgtype :: TypeExpression ('Required ('Null ty))
1157+instance PGTyped 'PGbool where pgtype = bool
1158+instance PGTyped 'PGint2 where pgtype = int2
1159+instance PGTyped 'PGint4 where pgtype = int4
1160+instance PGTyped 'PGint8 where pgtype = int8
1161+instance PGTyped 'PGnumeric where pgtype = numeric
1162+instance PGTyped 'PGfloat4 where pgtype = float4
1163+instance PGTyped 'PGfloat8 where pgtype = float8
1164+instance PGTyped 'PGtext where pgtype = text
1165+instance (KnownNat n, 1 <= n)
1166+ => PGTyped ('PGchar n) where pgtype = char (Proxy @n)
1167+instance (KnownNat n, 1 <= n)
1168+ => PGTyped ('PGvarchar n) where pgtype = varchar (Proxy @n)
1169+instance PGTyped 'PGbytea where pgtype = bytea
1170+instance PGTyped 'PGtimestamp where pgtype = timestamp
1171+instance PGTyped 'PGtimestamptz where pgtype = timestampWithTimeZone
1172+instance PGTyped 'PGdate where pgtype = date
1173+instance PGTyped 'PGtime where pgtype = time
1174+instance PGTyped 'PGtimetz where pgtype = timeWithTimeZone
1175+instance PGTyped 'PGinterval where pgtype = interval
1176+instance PGTyped 'PGuuid where pgtype = uuid
1177+instance PGTyped 'PGjson where pgtype = json
1178+instance PGTyped 'PGjsonb where pgtype = jsonb
1179+
1180+{-----------------------------------------
1181+statements
1182+-----------------------------------------}
1183+
1184+newtype Definition
1185+ (schema0 :: TablesType)
1186+ (schema1 :: TablesType)
1187+ = UnsafeDefinition { renderDefinition :: ByteString }
1188+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1189+
1190+instance Category Definition where
1191+ id = UnsafeDefinition ";"
1192+ ddl1 . ddl0 = UnsafeDefinition $
1193+ renderDefinition ddl0 <+> renderDefinition ddl1
1194+
1195+newtype Manipulation
1196+ (schema :: TablesType)
1197+ (params :: [ColumnType])
1198+ (columns :: ColumnsType)
1199+ = UnsafeManipulation { renderManipulation :: ByteString }
1200+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1201+
1202+newtype Query
1203+ (schema :: TablesType)
1204+ (params :: [ColumnType])
1205+ (columns :: ColumnsType)
1206+ = UnsafeQuery { renderQuery :: ByteString }
1207+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1208+
1209+queryStatement
1210+ :: Query schema params columns
1211+ -> Manipulation schema params columns
1212+queryStatement q = UnsafeManipulation $ renderQuery q <> ";"
1213+
1214+union, unionAll, intersect, intersectAll, except, exceptAll
1215+ :: Query schema params columns
1216+ -> Query schema params columns
1217+ -> Query schema params columns
1218+q1 `union` q2 = UnsafeQuery $
1219+ parenthesized (renderQuery q1)
1220+ <+> "UNION"
1221+ <+> parenthesized (renderQuery q2)
1222+q1 `unionAll` q2 = UnsafeQuery $
1223+ parenthesized (renderQuery q1)
1224+ <+> "UNION" <+> "ALL"
1225+ <+> parenthesized (renderQuery q2)
1226+q1 `intersect` q2 = UnsafeQuery $
1227+ parenthesized (renderQuery q1)
1228+ <+> "INTERSECT"
1229+ <+> parenthesized (renderQuery q2)
1230+q1 `intersectAll` q2 = UnsafeQuery $
1231+ parenthesized (renderQuery q1)
1232+ <+> "INTERSECT" <+> "ALL"
1233+ <+> parenthesized (renderQuery q2)
1234+q1 `except` q2 = UnsafeQuery $
1235+ parenthesized (renderQuery q1)
1236+ <+> "EXCEPT"
1237+ <+> parenthesized (renderQuery q2)
1238+q1 `exceptAll` q2 = UnsafeQuery $
1239+ parenthesized (renderQuery q1)
1240+ <+> "EXCEPT" <+> "ALL"
1241+ <+> parenthesized (renderQuery q2)
1242+
1243+{-----------------------------------------
1244+SELECT statements
1245+-----------------------------------------}
1246+
1247+select
1248+ :: SListI columns
1249+ => NP (Aliased (Expression params tables grouping)) (column ': columns)
1250+ -> TableExpression params schema tables grouping
1251+ -> Query schema params (column ': columns)
1252+select list tabs = UnsafeQuery $
1253+ "SELECT"
1254+ <+> renderCommaSeparated (renderAliased renderExpression) list
1255+ <+> renderTableExpression tabs
1256+
1257+selectDistinct
1258+ :: SListI columns
1259+ => NP (Aliased (Expression params tables grouping)) (column ': columns)
1260+ -> TableExpression params schema tables grouping
1261+ -> Query schema params (column ': columns)
1262+selectDistinct list tabs = UnsafeQuery $
1263+ "SELECT DISTINCT"
1264+ <+> renderCommaSeparated (renderAliased renderExpression) list
1265+ <+> renderTableExpression tabs
1266+
1267+selectStar
1268+ :: tables ~ '[table ::: columns]
1269+ => TableExpression params schema tables 'Ungrouped
1270+ -> Query schema params columns
1271+selectStar tabs = UnsafeQuery $ "SELECT" <+> "*" <+> renderTableExpression tabs
1272+
1273+selectDistinctStar
1274+ :: tables ~ '[table ::: columns]
1275+ => TableExpression params schema tables 'Ungrouped
1276+ -> Query schema params columns
1277+selectDistinctStar tabs = UnsafeQuery $
1278+ "SELECT DISTINCT" <+> "*" <+> renderTableExpression tabs
1279+
1280+selectDotStar
1281+ :: HasTable table tables columns
1282+ => Alias table
1283+ -> TableExpression params schema tables 'Ungrouped
1284+ -> Query schema params columns
1285+selectDotStar table tables = UnsafeQuery $
1286+ "SELECT" <+> renderAlias table <> ".*" <+> renderTableExpression tables
1287+
1288+selectDistinctDotStar
1289+ :: HasTable table tables columns
1290+ => Alias table
1291+ -> TableExpression params schema tables 'Ungrouped
1292+ -> Query schema params columns
1293+selectDistinctDotStar table tables = UnsafeQuery $
1294+ "SELECT DISTINCT" <+> renderAlias table <> ".*"
1295+ <+> renderTableExpression tables
1296+
1297+{-----------------------------------------
1298+CREATE statements
1299+-----------------------------------------}
1300+
1301+createTable
1302+ :: (KnownSymbol table, SListI columns)
1303+ => Alias table
1304+ -> NP (Aliased TypeExpression) (column ': columns)
1305+ -> [TableConstraint schema (column ': columns)]
1306+ -> Definition schema (Create table (column ': columns) schema)
1307+createTable table columns constraints = UnsafeDefinition $
1308+ "CREATE TABLE" <+> renderAlias table
1309+ <+> parenthesized
1310+ ( renderCommaSeparated renderColumnDef columns
1311+ <> renderConstraints constraints )
1312+ <> ";"
1313+ where
1314+ renderColumnDef :: Aliased TypeExpression x -> ByteString
1315+ renderColumnDef (ty `As` column) =
1316+ renderAlias column <+> renderTypeExpression ty
1317+ renderConstraints :: [TableConstraint schema columns] -> ByteString
1318+ renderConstraints = \case
1319+ [] -> ""
1320+ _ -> ", " <> commaSeparated (renderTableConstraint <$> constraints)
1321+
1322+newtype TableConstraint
1323+ (schema :: TablesType)
1324+ (columns :: ColumnsType)
1325+ = UnsafeTableConstraint { renderTableConstraint :: ByteString }
1326+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1327+
1328+check
1329+ :: Condition '[] '[table ::: columns] 'Ungrouped
1330+ -> TableConstraint schema columns
1331+check condition = UnsafeTableConstraint $
1332+ "CHECK" <+> parenthesized (renderExpression condition)
1333+
1334+renderColumn :: Column columns columnty -> ByteString
1335+renderColumn (Column column) = renderAlias column
1336+
1337+unique
1338+ :: SListI subcolumns
1339+ => NP (Column columns) subcolumns
1340+ -> TableConstraint schema columns
1341+unique columns = UnsafeTableConstraint $
1342+ "UNIQUE" <+> parenthesized (renderCommaSeparated renderColumn columns)
1343+
1344+primaryKey
1345+ :: (SListI subcolumns, AllNotNull subcolumns)
1346+ => NP (Column columns) subcolumns
1347+ -> TableConstraint schema columns
1348+primaryKey columns = UnsafeTableConstraint $
1349+ "PRIMARY KEY" <+> parenthesized (renderCommaSeparated renderColumn columns)
1350+
1351+foreignKey
1352+ :: ( HasTable reftable schema refcolumns
1353+ , SameTypes subcolumns refsubcolumns
1354+ , NotAllNull subcolumns
1355+ , SListI subcolumns
1356+ , SListI refsubcolumns)
1357+ => NP (Column columns) subcolumns
1358+ -> Alias reftable
1359+ -> NP (Column refcolumns) refsubcolumns
1360+ -> OnDelete
1361+ -> OnUpdate
1362+ -> TableConstraint schema columns
1363+foreignKey columns reftable refcolumns onDelete onUpdate =
1364+ UnsafeTableConstraint $
1365+ "FOREIGN KEY" <+> parenthesized (renderCommaSeparated renderColumn columns)
1366+ <+> "REFERENCES" <+> renderAlias reftable
1367+ <+> parenthesized (renderCommaSeparated renderColumn refcolumns)
1368+ <+> renderOnDelete onDelete
1369+ <+> renderOnUpdate onUpdate
1370+
1371+data OnDelete
1372+ = OnDeleteNoAction
1373+ | OnDeleteRestrict
1374+ | OnDeleteCascade
1375+ deriving (GHC.Generic,Show,Eq,Ord,Data)
1376+instance NFData OnDelete
1377+
1378+renderOnDelete :: OnDelete -> ByteString
1379+renderOnDelete = \case
1380+ OnDeleteNoAction -> "ON DELETE NO ACTION"
1381+ OnDeleteRestrict -> "ON DELETE RESTRICT"
1382+ OnDeleteCascade -> "ON DELETE CASCADE"
1383+
1384+data OnUpdate
1385+ = OnUpdateNoAction
1386+ | OnUpdateRestrict
1387+ | OnUpdateCascade
1388+ deriving (GHC.Generic,Show,Eq,Ord,Data)
1389+instance NFData OnUpdate
1390+
1391+renderOnUpdate :: OnUpdate -> ByteString
1392+renderOnUpdate = \case
1393+ OnUpdateNoAction -> "ON UPDATE NO ACTION"
1394+ OnUpdateRestrict -> "ON UPDATE RESTRICT"
1395+ OnUpdateCascade -> "ON UPDATE CASCADE"
1396+
1397+{-----------------------------------------
1398+DROP statements
1399+-----------------------------------------}
1400+
1401+dropTable
1402+ :: KnownSymbol table
1403+ => Alias table
1404+ -> Definition schema (Drop table schema)
1405+dropTable table = UnsafeDefinition $ "DROP TABLE" <+> renderAlias table <> ";"
1406+
1407+{-----------------------------------------
1408+ALTER statements
1409+-----------------------------------------}
1410+
1411+alterTable
1412+ :: HasTable table schema columns0
1413+ => Alias table
1414+ -> AlterTable columns0 columns1
1415+ -> Definition schema (Alter table schema columns1)
1416+alterTable table alteration = UnsafeDefinition $
1417+ "ALTER TABLE"
1418+ <+> renderAlias table
1419+ <+> renderAlterTable alteration
1420+ <> ";"
1421+
1422+alterTableRename
1423+ :: (KnownSymbol table0, KnownSymbol table1)
1424+ => Alias table0
1425+ -> Alias table1
1426+ -> Definition schema (Rename table0 table1 schema)
1427+alterTableRename table0 table1 = UnsafeDefinition $
1428+ "ALTER TABLE" <+> renderAlias table0
1429+ <+> "RENAME TO" <+> renderAlias table1 <> ";"
1430+
1431+newtype AlterTable
1432+ (columns0 :: [(Symbol, ColumnType)])
1433+ (columns1 :: [(Symbol, ColumnType)]) =
1434+ UnsafeAlterTable {renderAlterTable :: ByteString}
1435+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1436+
1437+addColumnDefault
1438+ :: KnownSymbol column
1439+ => Alias column
1440+ -> TypeExpression ('Optional ty)
1441+ -> AlterTable columns (Create column ('Optional ty) columns)
1442+addColumnDefault column ty = UnsafeAlterTable $
1443+ "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
1444+
1445+addColumnNull
1446+ :: KnownSymbol column
1447+ => Alias column
1448+ -> TypeExpression ('Required ('Null ty))
1449+ -> AlterTable columns (Create column ('Required ('Null ty)) columns)
1450+addColumnNull column ty = UnsafeAlterTable $
1451+ "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
1452+
1453+dropColumn
1454+ :: KnownSymbol column
1455+ => Alias column
1456+ -> AlterTable columns (Drop column columns)
1457+dropColumn column = UnsafeAlterTable $
1458+ "DROP COLUMN" <+> renderAlias column
1459+
1460+renameColumn
1461+ :: (KnownSymbol column0, KnownSymbol column1)
1462+ => Alias column0
1463+ -> Alias column1
1464+ -> AlterTable columns (Rename column0 column1 columns)
1465+renameColumn column0 column1 = UnsafeAlterTable $
1466+ "RENAME COLUMN" <+> renderAlias column0 <+> "TO" <+> renderAlias column1
1467+
1468+alterColumn
1469+ :: (KnownSymbol column, HasColumn column columns ty0)
1470+ => Alias column
1471+ -> AlterColumn ty0 ty1
1472+ -> AlterTable columns (Alter column columns ty1)
1473+alterColumn column alteration = UnsafeAlterTable $
1474+ "ALTER COLUMN" <+> renderAlias column <+> renderAlterColumn alteration
1475+
1476+newtype AlterColumn (ty0 :: ColumnType) (ty1 :: ColumnType) =
1477+ UnsafeAlterColumn {renderAlterColumn :: ByteString}
1478+ deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1479+
1480+setDefault
1481+ :: Expression '[] '[] 'Ungrouped ('Required ty)
1482+ -> AlterColumn ('Required ty) ('Optional ty)
1483+setDefault expression = UnsafeAlterColumn $
1484+ "SET DEFAULT" <+> renderExpression expression
1485+
1486+dropDefault :: AlterColumn ('Optional ty) ('Required ty)
1487+dropDefault = UnsafeAlterColumn $ "DROP DEFAULT"
1488+
1489+setNotNull
1490+ :: AlterColumn (optionality ('Null ty)) (optionality ('NotNull ty))
1491+setNotNull = UnsafeAlterColumn $ "SET NOT NULL"
1492+
1493+dropNotNull
1494+ :: AlterColumn (optionality ('NotNull ty)) (optionality ('Null ty))
1495+dropNotNull = UnsafeAlterColumn $ "DROP NOT NULL"
1496+
1497+alterType
1498+ :: TypeExpression (optionality (nullity ty1))
1499+ -> AlterColumn (optionality (nullity ty0)) (optionality (nullity ty1))
1500+alterType ty = UnsafeAlterColumn $ "TYPE " <> renderTypeExpression ty
1501+
1502+{-----------------------------------------
1503+INSERT statements
1504+-----------------------------------------}
1505+
1506+insertInto
1507+ :: (SListI columns, SListI results, HasTable table schema columns)
1508+ => Alias table
1509+ -> ValuesClause schema params columns
1510+ -> ConflictClause params columns
1511+ -> ReturningClause params columns results
1512+ -> Manipulation schema params results
1513+insertInto table insert conflict returning = UnsafeManipulation $
1514+ "INSERT" <+> "INTO" <+> renderAlias table
1515+ <+> renderValuesClause insert
1516+ <> renderConflictClause conflict
1517+ <> renderReturningClause returning
1518+
1519+data ValuesClause
1520+ (schema :: TablesType)
1521+ (params :: [ColumnType])
1522+ (columns :: ColumnsType)
1523+ = Values
1524+ (NP (Aliased (Expression params '[] 'Ungrouped)) columns)
1525+ [NP (Aliased (Expression params '[] 'Ungrouped)) columns]
1526+ | ValuesQuery (Query schema params columns)
1527+
1528+renderValuesClause
1529+ :: SListI columns
1530+ => ValuesClause schema params columns
1531+ -> ByteString
1532+renderValuesClause = \case
1533+ Values row rows ->
1534+ parenthesized (renderCommaSeparated renderAliasPart row)
1535+ <+> "VALUES"
1536+ <+> commaSeparated
1537+ (parenthesized . renderCommaSeparated renderValuePart <$> row:rows)
1538+ where
1539+ renderAliasPart, renderValuePart
1540+ :: Aliased (Expression params '[] 'Ungrouped) ty -> ByteString
1541+ renderAliasPart (_ `As` name) = renderAlias name
1542+ renderValuePart (value `As` _) = renderExpression value
1543+ ValuesQuery q -> renderQuery q
1544+
1545+data ReturningClause
1546+ (params :: [ColumnType])
1547+ (columns :: ColumnsType)
1548+ (results :: ColumnsType)
1549+ where
1550+ ReturningStar :: ReturningClause params columns columns
1551+ Returning
1552+ :: NP
1553+ (Aliased (Expression params '[table ::: columns] 'Ungrouped))
1554+ results
1555+ -> ReturningClause params columns results
1556+
1557+renderReturningClause
1558+ :: SListI results
1559+ => ReturningClause params columns results
1560+ -> ByteString
1561+renderReturningClause = \case
1562+ ReturningStar -> " RETURNING *;"
1563+ Returning Nil -> ";"
1564+ Returning results -> " RETURNING"
1565+ <+> renderCommaSeparated (renderAliased renderExpression) results <> ";"
1566+
1567+data ConflictClause params columns where
1568+ Conflict :: ConflictClause params columns
1569+ OnConflictDoNothing :: ConflictClause params columns
1570+ OnConflictDoUpdate
1571+ :: NP (Aliased (UpdateExpression params columns)) columns
1572+ -> Maybe (Condition params '[table ::: columns] 'Ungrouped)
1573+ -> ConflictClause params columns
1574+
1575+renderConflictClause
1576+ :: SListI columns
1577+ => ConflictClause params columns
1578+ -> ByteString
1579+renderConflictClause = \case
1580+ Conflict -> ""
1581+ OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
1582+ OnConflictDoUpdate updates whMaybe
1583+ -> " ON CONFLICT DO UPDATE SET"
1584+ <+> renderCommaSeparatedMaybe renderUpdateExpression updates
1585+ <> case whMaybe of
1586+ Nothing -> ""
1587+ Just wh -> " WHERE" <+> renderExpression wh
1588+
1589+{-----------------------------------------
1590+UPDATE statements
1591+-----------------------------------------}
1592+
1593+data UpdateExpression params columns ty
1594+ = Same
1595+ | Set (forall table. Expression params '[table ::: columns] 'Ungrouped ty)
1596+deriving instance Show (UpdateExpression params columns ty)
1597+deriving instance Eq (UpdateExpression params columns ty)
1598+deriving instance Ord (UpdateExpression params columns ty)
1599+
1600+renderUpdateExpression
1601+ :: Aliased (UpdateExpression params columns) column
1602+ -> Maybe ByteString
1603+renderUpdateExpression = \case
1604+ Same `As` _ -> Nothing
1605+ Set expression `As` column -> Just $
1606+ renderAlias column <+> "=" <+> renderExpression expression
1607+
1608+update
1609+ :: (HasTable table schema columns, SListI columns, SListI results)
1610+ => Alias table
1611+ -> NP (Aliased (UpdateExpression params columns)) columns
1612+ -> Condition params '[tab ::: columns] 'Ungrouped
1613+ -> ReturningClause params columns results
1614+ -> Manipulation schema params results
1615+update table columns wh returning = UnsafeManipulation $
1616+ "UPDATE"
1617+ <+> renderAlias table
1618+ <+> "SET"
1619+ <+> renderCommaSeparatedMaybe renderUpdateExpression columns
1620+ <+> "WHERE" <+> renderExpression wh
1621+ <> renderReturningClause returning
1622+
1623+{-----------------------------------------
1624+DELETE statements
1625+-----------------------------------------}
1626+
1627+deleteFrom
1628+ :: HasTable table schema columns
1629+ => Alias table
1630+ -> Condition params '[table ::: columns] 'Ungrouped
1631+ -> Manipulation schema params '[]
1632+deleteFrom table wh = UnsafeManipulation $
1633+ "DELETE FROM" <+> renderAlias table
1634+ <+> "WHERE" <+> renderExpression wh <> ";"
1635+
1636+{-----------------------------------------
1637+render helper functions
1638+-----------------------------------------}
1639+
1640+parenthesized :: ByteString -> ByteString
1641+parenthesized str = "(" <> str <> ")"
1642+
1643+(<+>) :: ByteString -> ByteString -> ByteString
1644+str1 <+> str2 = str1 <> " " <> str2
1645+
1646+commaSeparated :: [ByteString] -> ByteString
1647+commaSeparated = ByteString.intercalate ", "
1648+
1649+renderCommaSeparated
1650+ :: SListI xs
1651+ => (forall x. expression x -> ByteString)
1652+ -> NP expression xs -> ByteString
1653+renderCommaSeparated render
1654+ = commaSeparated
1655+ . hcollapse
1656+ . hmap (K . render)
1657+
1658+renderCommaSeparatedMaybe
1659+ :: SListI xs
1660+ => (forall x. expression x -> Maybe ByteString)
1661+ -> NP expression xs -> ByteString
1662+renderCommaSeparatedMaybe render
1663+ = commaSeparated
1664+ . catMaybes
1665+ . hcollapse
1666+ . hmap (K . render)
1667+
1668+renderNat :: KnownNat n => proxy n -> ByteString
1669+renderNat (_ :: proxy n) = fromString (show (natVal' (proxy# :: Proxy# n)))