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