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