@@ -0,0 +1,1310 @@
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 (proxy# @Symbol @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 '[] 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 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+
346+(/=*)
347+ :: Expression params tables grouping ('Required (nullity ty))
348+ -> Expression params tables grouping ('Required (nullity ty))
349+ -> Expression params tables grouping ('Required (nullity 'PGbool))
350+(/=*) = unsafeBinaryOp "<>"
351+
352+(>=*)
353+ :: Expression params tables grouping ('Required (nullity ty))
354+ -> Expression params tables grouping ('Required (nullity ty))
355+ -> Expression params tables grouping ('Required (nullity 'PGbool))
356+(>=*) = unsafeBinaryOp ">="
357+
358+(<*)
359+ :: Expression params tables grouping ('Required (nullity ty))
360+ -> Expression params tables grouping ('Required (nullity ty))
361+ -> Expression params tables grouping ('Required (nullity 'PGbool))
362+(<*) = unsafeBinaryOp "<"
363+
364+(<=*)
365+ :: Expression params tables grouping ('Required (nullity ty))
366+ -> Expression params tables grouping ('Required (nullity ty))
367+ -> Expression params tables grouping ('Required (nullity 'PGbool))
368+(<=*) = unsafeBinaryOp "<="
369+
370+(>*)
371+ :: Expression params tables grouping ('Required (nullity ty))
372+ -> Expression params tables grouping ('Required (nullity ty))
373+ -> Expression params tables grouping ('Required (nullity 'PGbool))
374+(>*) = unsafeBinaryOp ">"
375+
376+data SortExpression params tables grouping where
377+ Asc
378+ :: Expression params tables grouping ty
379+ -> SortExpression params tables grouping
380+ Desc
381+ :: Expression params tables grouping ty
382+ -> SortExpression params tables grouping
383+
384+renderSortExpression :: SortExpression params tables grouping -> ByteString
385+renderSortExpression = \case
386+ Asc expression -> renderExpression expression <+> "ASC"
387+ Desc expression -> renderExpression expression <+> "DESC"
388+
389+{-----------------------------------------
390+aggregation
391+-----------------------------------------}
392+
393+data By
394+ (tables :: [(Symbol,[(Symbol,ColumnType)])])
395+ (tabcol :: (Symbol,Symbol)) where
396+ By
397+ :: (tables ~ '[table ::: columns], HasColumn column columns ty)
398+ => Alias column
399+ -> By tables '(table, column)
400+ By2
401+ :: (HasTable table tables columns, HasColumn column columns ty)
402+ => (Alias table, Alias column)
403+ -> By tables '(table, column)
404+
405+renderBy :: By tables tabcolty -> ByteString
406+renderBy = \case
407+ By column -> renderAlias column
408+ By2 (table, column) -> renderAlias table <> "." <> renderAlias column
409+
410+data GroupByClause tables grouping where
411+ NoGroups :: GroupByClause tables 'Ungrouped
412+ Group
413+ :: SListI bys
414+ => NP (By tables) bys
415+ -> GroupByClause tables ('Grouped bys)
416+
417+renderGroupByClause :: GroupByClause tables grouping -> ByteString
418+renderGroupByClause = \case
419+ NoGroups -> ""
420+ Group Nil -> ""
421+ Group bys -> " GROUP BY" <+> renderCommaSeparated renderBy bys
422+
423+data HavingClause params tables grouping where
424+ NoHaving :: HavingClause params tables 'Ungrouped
425+ Having
426+ :: [Condition params tables ('Grouped bys)]
427+ -> HavingClause params tables ('Grouped bys)
428+
429+renderHavingClause :: HavingClause params tables grouping -> ByteString
430+renderHavingClause = \case
431+ NoHaving -> ""
432+ Having [] -> ""
433+ Having conditions ->
434+ " HAVING" <+> commaSeparated (renderExpression <$> conditions)
435+
436+class (KnownSymbol table, KnownSymbol column)
437+ => GroupedBy table column bys where
438+ getGroup1
439+ :: (tables ~ '[table ::: columns], HasColumn column columns ty)
440+ => Proxy# column
441+ -> Expression params tables ('Grouped bys) ty
442+ getGroup1 column = UnsafeExpression $ renderSymbol column
443+ getGroup2
444+ :: (HasTable table tables columns, HasColumn column columns ty)
445+ => Alias table
446+ -> Alias column
447+ -> Expression params tables ('Grouped bys) ty
448+ getGroup2 table column = UnsafeExpression $
449+ renderAlias table <> "." <> renderAlias column
450+instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
451+ => GroupedBy table column ('(table,column) ': bys)
452+instance {-# OVERLAPPABLE #-}
453+ ( KnownSymbol table
454+ , KnownSymbol column
455+ , GroupedBy table column bys
456+ ) => GroupedBy table column (tabcol ': bys)
457+
458+instance
459+ ( tables ~ '[table ::: columns]
460+ , HasColumn column columns ty
461+ , GroupedBy table column bys
462+ ) => IsLabel column
463+ (Expression params tables ('Grouped bys) ty) where
464+ fromLabel = getGroup1 (proxy# @Symbol @column)
465+
466+instance
467+ ( HasTable table tables columns
468+ , HasColumn column columns ty
469+ , GroupedBy table column bys
470+ ) => IsTableColumn table column
471+ (Expression params tables ('Grouped bys) ty) where (&.) = getGroup2
472+
473+unsafeAggregate
474+ :: ByteString
475+ -> Expression params tables 'Ungrouped ('Required xty)
476+ -> Expression params tables ('Grouped bys) ('Required yty)
477+unsafeAggregate fun x = UnsafeExpression $ mconcat
478+ [fun, "(", renderExpression x, ")"]
479+
480+sum_
481+ :: PGNum ty
482+ => Expression params tables 'Ungrouped ('Required (nullity ty))
483+ -> Expression params tables ('Grouped bys) ('Required (nullity ty))
484+sum_ = unsafeAggregate "sum"
485+
486+avgInt
487+ :: PGIntegral int
488+ => Expression params tables 'Ungrouped ('Required (nullity int))
489+ -> Expression params tables ('Grouped bys) ('Required (nullity 'PGnumeric))
490+avgInt = unsafeAggregate "avg"
491+
492+avgFloat
493+ :: PGFloating float
494+ => Expression params tables 'Ungrouped ('Required (nullity float))
495+ -> Expression params tables ('Grouped bys) ('Required (nullity 'PGfloat8))
496+avgFloat = unsafeAggregate "avg"
497+
498+bitAnd
499+ :: PGIntegral int
500+ => Expression params tables 'Ungrouped ('Required (nullity int))
501+ -> Expression params tables ('Grouped bys) ('Required (nullity int))
502+bitAnd = unsafeAggregate "bit_and"
503+
504+bitOr
505+ :: PGIntegral int
506+ => Expression params tables 'Ungrouped ('Required (nullity int))
507+ -> Expression params tables ('Grouped bys) ('Required (nullity int))
508+bitOr = unsafeAggregate "bit_or"
509+
510+boolAnd
511+ :: Expression params tables 'Ungrouped ('Required (nullity 'PGbool))
512+ -> Expression params tables ('Grouped bys) ('Required (nullity 'PGbool))
513+boolAnd = unsafeAggregate "bool_and"
514+
515+boolOr
516+ :: Expression params tables 'Ungrouped ('Required (nullity 'PGbool))
517+ -> Expression params tables ('Grouped bys) ('Required (nullity 'PGbool))
518+boolOr = unsafeAggregate "bool_or"
519+
520+countStar
521+ :: Expression params table ('Grouped bys) ('Required ('NotNull 'PGint8))
522+countStar = UnsafeExpression "count(*)"
523+
524+count
525+ :: Expression params tables 'Ungrouped ('Required ty)
526+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGint8))
527+count = unsafeAggregate "count"
528+
529+every
530+ :: Expression params tables 'Ungrouped ('Required (nullity 'PGbool))
531+ -> Expression params tables ('Grouped bys) ('Required (nullity 'PGbool))
532+every = unsafeAggregate "every"
533+
534+jsonAgg
535+ :: Expression params tables 'Ungrouped ('Required ty)
536+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjson))
537+jsonAgg = unsafeAggregate "json_agg"
538+
539+jsonbAgg
540+ :: Expression params tables 'Ungrouped ('Required ty)
541+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjsonb))
542+jsonbAgg = unsafeAggregate "jsonb_agg"
543+
544+jsonObjectAgg
545+ :: Expression params tables 'Ungrouped ('Required ('NotNull keyty))
546+ -> Expression params tables 'Ungrouped ('Required valty)
547+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjson))
548+jsonObjectAgg k v = UnsafeExpression $
549+ "json_object_agg(" <> renderExpression k
550+ <> ", " <> renderExpression v <> ")"
551+
552+jsonbObjectAgg
553+ :: Expression params tables 'Ungrouped ('Required ('NotNull keyty))
554+ -> Expression params tables 'Ungrouped ('Required valty)
555+ -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjsonb))
556+jsonbObjectAgg k v = UnsafeExpression $
557+ "jsonb_object_agg(" <> renderExpression k
558+ <> ", " <> renderExpression v <> ")"
559+
560+max_
561+ :: Expression params tables 'Ungrouped ('Required (nullity ty))
562+ -> Expression params tables ('Grouped bys) ('Required (nullity ty))
563+max_ = unsafeAggregate "max"
564+
565+min_
566+ :: Expression params tables 'Ungrouped ('Required (nullity ty))
567+ -> Expression params tables ('Grouped bys) ('Required (nullity ty))
568+min_ = unsafeAggregate "min"
569+
570+{-----------------------------------------
571+tables
572+-----------------------------------------}
573+
574+newtype Table
575+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
576+ (columns :: [(Symbol,ColumnType)])
577+ = UnsafeTable { renderTable :: ByteString }
578+ deriving (Show,Eq)
579+
580+class KnownSymbol table => HasTable table tables columns
581+ | table tables -> columns where
582+ getTable :: Proxy# table -> Table tables columns
583+ getTable table = UnsafeTable $ renderSymbol table
584+instance {-# OVERLAPPING #-} KnownSymbol table
585+ => HasTable table ((table ::: columns) ': tables) columns
586+instance {-# OVERLAPPABLE #-}
587+ (KnownSymbol table, HasTable table schema columns)
588+ => HasTable table (table' ': schema) columns
589+
590+instance HasTable table schema columns
591+ => IsLabel table (Table schema columns) where
592+ fromLabel = getTable (proxy# @Symbol @table)
593+
594+data TableReference params schema tables where
595+ Table
596+ :: Aliased (Table schema) table
597+ -> TableReference params schema '[table]
598+ Subquery
599+ :: Aliased (Query schema params) table
600+ -> TableReference params schema '[table]
601+ CrossJoin
602+ :: TableReference params schema right
603+ -> TableReference params schema left
604+ -> TableReference params schema (Join left right)
605+ InnerJoin
606+ :: TableReference params schema right
607+ -> Condition params (Join left right) 'Ungrouped
608+ -> TableReference params schema left
609+ -> TableReference params schema (Join left right)
610+ LeftOuterJoin
611+ :: TableReference params schema right
612+ -> Condition params (Join left right) 'Ungrouped
613+ -> TableReference params schema left
614+ -> TableReference params schema (Join left (NullifyTables right))
615+ RightOuterJoin
616+ :: TableReference params schema right
617+ -> Condition params (Join left right) 'Ungrouped
618+ -> TableReference params schema left
619+ -> TableReference params schema (Join (NullifyTables left) right)
620+ FullOuterJoin
621+ :: TableReference params schema right
622+ -> Condition params (Join left right) 'Ungrouped
623+ -> TableReference params schema left
624+ -> TableReference params schema
625+ (Join (NullifyTables left) (NullifyTables right))
626+
627+renderTableReference :: TableReference params schema tables -> ByteString
628+renderTableReference = \case
629+ Table table -> renderAliased renderTable table
630+ Subquery selection -> renderAliased renderQuery selection
631+ CrossJoin right left ->
632+ renderTableReference left <+> "CROSS JOIN" <+> renderTableReference right
633+ InnerJoin right on left -> renderJoin "INNER JOIN" right on left
634+ LeftOuterJoin right on left -> renderJoin "LEFT OUTER JOIN" right on left
635+ RightOuterJoin right on left -> renderJoin "RIGHT OUTER JOIN" right on left
636+ FullOuterJoin right on left -> renderJoin "FULL OUTER JOIN" right on left
637+ where
638+ renderJoin op right on left =
639+ renderTableReference left <+> op <+> renderTableReference right
640+ <+> "ON" <+> renderExpression on
641+
642+data TableExpression
643+ (params :: [ColumnType])
644+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
645+ (tables :: [(Symbol,[(Symbol,ColumnType)])])
646+ (grouping :: Grouping)
647+ = TableExpression
648+ { fromClause :: TableReference params schema tables
649+ , whereClause :: [Condition params tables 'Ungrouped]
650+ , groupByClause :: GroupByClause tables grouping
651+ , havingClause :: HavingClause params tables grouping
652+ , orderByClause :: [SortExpression params tables grouping]
653+ , limitClause :: [Word64]
654+ , offsetClause :: [Word64]
655+ }
656+
657+renderTableExpression
658+ :: TableExpression params schema tables grouping
659+ -> ByteString
660+renderTableExpression
661+ (TableExpression tables whs' grps' hvs' srts' lims' offs') = mconcat
662+ [ "FROM ", renderTableReference tables
663+ , renderWheres whs'
664+ , renderGroupByClause grps'
665+ , renderHavingClause hvs'
666+ , renderSorts srts'
667+ , renderLimits lims'
668+ , renderOffsets offs'
669+ ]
670+ where
671+ renderWheres = \case
672+ [] -> ""
673+ wh:[] -> " WHERE" <+> renderExpression wh
674+ wh:whs -> " WHERE" <+> renderExpression (foldr (&&*) wh whs)
675+ renderSorts = \case
676+ [] -> ""
677+ srts -> " SORT BY"
678+ <+> commaSeparated (renderSortExpression <$> srts)
679+ renderLimits = \case
680+ [] -> ""
681+ lims -> " LIMIT" <+> fromString (show (minimum lims))
682+ renderOffsets = \case
683+ [] -> ""
684+ offs -> " OFFSET" <+> fromString (show (sum offs))
685+
686+from
687+ :: TableReference params schema tables
688+ -> TableExpression params schema tables 'Ungrouped
689+from tables = TableExpression tables [] NoGroups NoHaving [] [] []
690+
691+where_
692+ :: Condition params tables 'Ungrouped
693+ -> TableExpression params schema tables grouping
694+ -> TableExpression params schema tables grouping
695+where_ wh tables = tables {whereClause = wh : whereClause tables}
696+
697+group
698+ :: SListI bys
699+ => NP (By tables) bys
700+ -> TableExpression params schema tables 'Ungrouped
701+ -> TableExpression params schema tables ('Grouped bys)
702+group bys tables = TableExpression
703+ { fromClause = fromClause tables
704+ , whereClause = whereClause tables
705+ , groupByClause = Group bys
706+ , havingClause = Having []
707+ , orderByClause = []
708+ , limitClause = limitClause tables
709+ , offsetClause = offsetClause tables
710+ }
711+
712+having
713+ :: Condition params tables ('Grouped bys)
714+ -> TableExpression params schema tables ('Grouped bys)
715+ -> TableExpression params schema tables ('Grouped bys)
716+having hv tables = tables
717+ { havingClause = case havingClause tables of Having hvs -> Having (hv:hvs) }
718+
719+orderBy
720+ :: [SortExpression params tables grouping]
721+ -> TableExpression params schema tables grouping
722+ -> TableExpression params schema tables grouping
723+orderBy srts tables = tables {orderByClause = orderByClause tables ++ srts}
724+
725+limit
726+ :: Word64
727+ -> TableExpression params schema tables grouping
728+ -> TableExpression params schema tables grouping
729+limit lim tables = tables {limitClause = lim : limitClause tables}
730+
731+offset
732+ :: Word64
733+ -> TableExpression params schema tables grouping
734+ -> TableExpression params schema tables grouping
735+offset off tables = tables {offsetClause = off : offsetClause tables}
736+
737+{-----------------------------------------
738+type expressions
739+-----------------------------------------}
740+
741+newtype TypeExpression (ty :: ColumnType)
742+ = UnsafeTypeExpression { renderTypeExpression :: ByteString }
743+ deriving (Show,Eq)
744+
745+bool :: TypeExpression ('Required ('Null 'PGbool))
746+bool = UnsafeTypeExpression "bool"
747+int2 :: TypeExpression ('Required ('Null 'PGint2))
748+int2 = UnsafeTypeExpression "int2"
749+smallint :: TypeExpression ('Required ('Null 'PGint2))
750+smallint = UnsafeTypeExpression "smallint"
751+int4 :: TypeExpression ('Required ('Null 'PGint4))
752+int4 = UnsafeTypeExpression "int4"
753+int :: TypeExpression ('Required ('Null 'PGint4))
754+int = UnsafeTypeExpression "int"
755+integer :: TypeExpression ('Required ('Null 'PGint4))
756+integer = UnsafeTypeExpression "integer"
757+int8 :: TypeExpression ('Required ('Null 'PGint8))
758+int8 = UnsafeTypeExpression "int8"
759+bigint :: TypeExpression ('Required ('Null 'PGint8))
760+bigint = UnsafeTypeExpression "bigint"
761+numeric :: TypeExpression ('Required ('Null 'PGnumeric))
762+numeric = UnsafeTypeExpression "numeric"
763+float4 :: TypeExpression ('Required ('Null 'PGfloat4))
764+float4 = UnsafeTypeExpression "float4"
765+real :: TypeExpression ('Required ('Null 'PGfloat4))
766+real = UnsafeTypeExpression "real"
767+float8 :: TypeExpression ('Required ('Null 'PGfloat8))
768+float8 = UnsafeTypeExpression "float8"
769+doublePrecision :: TypeExpression ('Required ('Null 'PGfloat8))
770+doublePrecision = UnsafeTypeExpression "double precision"
771+serial2 :: TypeExpression ('Optional ('NotNull 'PGint2))
772+serial2 = UnsafeTypeExpression "serial2"
773+smallserial :: TypeExpression ('Optional ('NotNull 'PGint2))
774+smallserial = UnsafeTypeExpression "smallserial"
775+serial4 :: TypeExpression ('Optional ('NotNull 'PGint4))
776+serial4 = UnsafeTypeExpression "serial4"
777+serial :: TypeExpression ('Optional ('NotNull 'PGint4))
778+serial = UnsafeTypeExpression "serial"
779+serial8 :: TypeExpression ('Optional ('NotNull 'PGint8))
780+serial8 = UnsafeTypeExpression "serial8"
781+bigserial :: TypeExpression ('Optional ('NotNull 'PGint8))
782+bigserial = UnsafeTypeExpression "bigserial"
783+text :: TypeExpression ('Required ('Null 'PGtext))
784+text = UnsafeTypeExpression "text"
785+char
786+ :: KnownNat n
787+ => proxy n
788+ -> TypeExpression ('Required ('Null ('PGchar n)))
789+char p = UnsafeTypeExpression $ "char(" <> renderNat p <> ")"
790+character
791+ :: KnownNat n
792+ => proxy n
793+ -> TypeExpression ('Required ('Null ('PGchar n)))
794+character p = UnsafeTypeExpression $ "character(" <> renderNat p <> ")"
795+varchar
796+ :: KnownNat n
797+ => proxy n
798+ -> TypeExpression ('Required ('Null ('PGvarchar n)))
799+varchar p = UnsafeTypeExpression $ "varchar(" <> renderNat p <> ")"
800+characterVarying
801+ :: KnownNat n
802+ => proxy n
803+ -> TypeExpression ('Required ('Null ('PGvarchar n)))
804+characterVarying p = UnsafeTypeExpression $
805+ "character varying(" <> renderNat p <> ")"
806+bytea :: TypeExpression ('Required ('Null ('PGbytea)))
807+bytea = UnsafeTypeExpression "bytea"
808+timestamp :: TypeExpression ('Required ('Null ('PGtimestamp)))
809+timestamp = UnsafeTypeExpression "timestamp"
810+timestampWithTimeZone :: TypeExpression ('Required ('Null ('PGtimestamptz)))
811+timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
812+date :: TypeExpression ('Required ('Null ('PGdate)))
813+date = UnsafeTypeExpression "date"
814+time :: TypeExpression ('Required ('Null ('PGtime)))
815+time = UnsafeTypeExpression "time"
816+timeWithTimeZone :: TypeExpression ('Required ('Null ('PGtimetz)))
817+timeWithTimeZone = UnsafeTypeExpression "time with time zone"
818+interval :: TypeExpression ('Required ('Null ('PGinterval)))
819+interval = UnsafeTypeExpression "interval"
820+uuid :: TypeExpression ('Required ('Null ('PGuuid)))
821+uuid = UnsafeTypeExpression "uuid"
822+json :: TypeExpression ('Required ('Null ('PGjson)))
823+json = UnsafeTypeExpression "json"
824+jsonb :: TypeExpression ('Required ('Null ('PGjsonb)))
825+jsonb = UnsafeTypeExpression "jsonb"
826+
827+notNull
828+ :: TypeExpression (optionality ('Null ty))
829+ -> TypeExpression (optionality ('NotNull ty))
830+notNull ty = UnsafeTypeExpression $ renderTypeExpression ty <+> "NOT NULL"
831+default_
832+ :: Expression '[] '[] 'Ungrouped ('Required ty)
833+ -> TypeExpression ('Required ty)
834+ -> TypeExpression ('Optional ty)
835+default_ x ty = UnsafeTypeExpression $
836+ renderTypeExpression ty <+> "DEFAULT" <+> renderExpression x
837+
838+class PGTyped (ty :: PGType) where
839+ pgtype :: TypeExpression ('Required ('Null ty))
840+instance PGTyped 'PGbool where pgtype = bool
841+instance PGTyped 'PGint2 where pgtype = int2
842+instance PGTyped 'PGint4 where pgtype = int4
843+instance PGTyped 'PGint8 where pgtype = int8
844+instance PGTyped 'PGnumeric where pgtype = numeric
845+instance PGTyped 'PGfloat4 where pgtype = float4
846+instance PGTyped 'PGfloat8 where pgtype = float8
847+instance PGTyped 'PGtext where pgtype = text
848+instance KnownNat n => PGTyped ('PGchar n) where pgtype = char (Proxy @n)
849+instance KnownNat n => PGTyped ('PGvarchar n) where pgtype = varchar (Proxy @n)
850+instance PGTyped 'PGbytea where pgtype = bytea
851+instance PGTyped 'PGtimestamp where pgtype = timestamp
852+instance PGTyped 'PGtimestamptz where pgtype = timestampWithTimeZone
853+instance PGTyped 'PGdate where pgtype = date
854+instance PGTyped 'PGtime where pgtype = time
855+instance PGTyped 'PGtimetz where pgtype = timeWithTimeZone
856+instance PGTyped 'PGinterval where pgtype = interval
857+instance PGTyped 'PGuuid where pgtype = uuid
858+instance PGTyped 'PGjson where pgtype = json
859+instance PGTyped 'PGjsonb where pgtype = jsonb
860+
861+{-----------------------------------------
862+statements
863+-----------------------------------------}
864+
865+newtype Definition
866+ (schema0 :: [(Symbol,[(Symbol,ColumnType)])])
867+ (schema1 :: [(Symbol,[(Symbol,ColumnType)])])
868+ = UnsafeDefinition { renderDefinition :: ByteString }
869+
870+instance Category Definition where
871+ id = UnsafeDefinition ";"
872+ ddl1 . ddl0 = UnsafeDefinition $
873+ renderDefinition ddl0 <+> renderDefinition ddl1
874+
875+newtype Manipulation
876+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
877+ (params :: [ColumnType])
878+ (columns :: [(Symbol,ColumnType)])
879+ = UnsafeManipulation { renderManipulation :: ByteString }
880+
881+newtype Query
882+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
883+ (params :: [ColumnType])
884+ (columns :: [(Symbol,ColumnType)])
885+ = UnsafeQuery { renderQuery :: ByteString }
886+
887+query
888+ :: Query schema params columns
889+ -> Manipulation schema params columns
890+query q = UnsafeManipulation $ renderQuery q <> ";"
891+
892+union, unionAll, intersect, intersectAll, except, exceptAll
893+ :: Query schema params columns
894+ -> Query schema params columns
895+ -> Query schema params columns
896+q1 `union` q2 = UnsafeQuery $
897+ parenthesized (renderQuery q1)
898+ <+> "UNION"
899+ <+> parenthesized (renderQuery q2)
900+q1 `unionAll` q2 = UnsafeQuery $
901+ parenthesized (renderQuery q1)
902+ <+> "UNION" <+> "ALL"
903+ <+> parenthesized (renderQuery q2)
904+q1 `intersect` q2 = UnsafeQuery $
905+ parenthesized (renderQuery q1)
906+ <+> "INTERSECT"
907+ <+> parenthesized (renderQuery q2)
908+q1 `intersectAll` q2 = UnsafeQuery $
909+ parenthesized (renderQuery q1)
910+ <+> "INTERSECT" <+> "ALL"
911+ <+> parenthesized (renderQuery q2)
912+q1 `except` q2 = UnsafeQuery $
913+ parenthesized (renderQuery q1)
914+ <+> "EXCEPT"
915+ <+> parenthesized (renderQuery q2)
916+q1 `exceptAll` q2 = UnsafeQuery $
917+ parenthesized (renderQuery q1)
918+ <+> "EXCEPT" <+> "ALL"
919+ <+> parenthesized (renderQuery q2)
920+
921+{-----------------------------------------
922+SELECT statements
923+-----------------------------------------}
924+
925+select
926+ :: SListI columns
927+ => NP (Aliased (Expression params tables grouping)) (column ': columns)
928+ -> TableExpression params schema tables grouping
929+ -> Query schema params (column ': columns)
930+select list tabs = UnsafeQuery $
931+ "SELECT"
932+ <+> renderCommaSeparated (renderAliased renderExpression) list
933+ <+> renderTableExpression tabs
934+
935+selectStar
936+ :: (tables ~ '[table ::: columns])
937+ => TableExpression params schema tables 'Ungrouped
938+ -> Query schema params columns
939+selectStar tabs = UnsafeQuery $ "SELECT" <+> "*" <+> renderTableExpression tabs
940+
941+selectDotStar
942+ :: HasTable table tables columns
943+ => Alias table
944+ -> TableExpression params schema tables 'Ungrouped
945+ -> Query schema params columns
946+selectDotStar table tables = UnsafeQuery $
947+ "SELECT" <+> renderAlias table <> ".*" <+> renderTableExpression tables
948+
949+selectRow
950+ :: SListI columns
951+ => NP (Aliased (Expression params '[] 'Ungrouped)) columns
952+ -> Query schema params columns
953+selectRow row = UnsafeQuery $
954+ "SELECT" <+> renderCommaSeparated (renderAliased renderExpression) row
955+
956+{-----------------------------------------
957+CREATE statements
958+-----------------------------------------}
959+
960+createTable
961+ :: (KnownSymbol table, SListI columns)
962+ => Alias table
963+ -> NP (Aliased TypeExpression) (column ': columns)
964+ -> [TableConstraint schema (column ': columns)]
965+ -> Definition schema (Create table (column ': columns) schema)
966+createTable table columns constraints = UnsafeDefinition $
967+ "CREATE TABLE" <+> renderAlias table
968+ <+> parenthesized
969+ ( renderCommaSeparated renderColumnDef columns
970+ <> renderConstraints constraints )
971+ <> ";"
972+ where
973+ renderColumnDef :: Aliased TypeExpression x -> ByteString
974+ renderColumnDef (ty `As` column) =
975+ renderAlias column <+> renderTypeExpression ty
976+ renderConstraints :: [TableConstraint schema columns] -> ByteString
977+ renderConstraints = \case
978+ [] -> ""
979+ _ -> ", " <> commaSeparated (renderTableConstraint <$> constraints)
980+
981+data TableConstraint
982+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
983+ (columns :: [(Symbol,ColumnType)])
984+ = UnsafeTableConstraint { renderTableConstraint :: ByteString }
985+
986+check
987+ :: Condition '[] '[table ::: columns] 'Ungrouped
988+ -> TableConstraint schema columns
989+check condition = UnsafeTableConstraint $
990+ "CHECK" <+> parenthesized (renderExpression condition)
991+
992+data Column
993+ (columns :: [(Symbol,ColumnType)])
994+ (columnty :: (Symbol,ColumnType))
995+ where
996+ Column
997+ :: HasColumn column columns ty
998+ => Alias column
999+ -> Column columns (column ::: ty)
1000+
1001+renderColumn :: Column columns columnty -> ByteString
1002+renderColumn (Column column) = renderAlias column
1003+
1004+unique
1005+ :: SListI subcolumns
1006+ => NP (Column columns) subcolumns
1007+ -> TableConstraint schema columns
1008+unique columns = UnsafeTableConstraint $
1009+ "UNIQUE" <+> parenthesized (renderCommaSeparated renderColumn columns)
1010+
1011+primaryKey
1012+ :: (SListI subcolumns, NotNullTypes subcolumns)
1013+ => NP (Column columns) subcolumns
1014+ -> TableConstraint schema columns
1015+primaryKey columns = UnsafeTableConstraint $
1016+ "PRIMARY KEY" <+> parenthesized (renderCommaSeparated renderColumn columns)
1017+
1018+type family SameTypes columns0 columns1 :: Constraint where
1019+ SameTypes '[] '[] = ()
1020+ SameTypes ((column0 ::: ty0) ': columns0) ((column1 ::: ty1) ': columns1)
1021+ = (ty0 ~ ty1, SameTypes columns0 columns1)
1022+
1023+type family NotNullTypes columns :: Constraint where
1024+ NotNullTypes '[] = ()
1025+ NotNullTypes ((column ::: (optionality ('NotNull ty))) ': columns)
1026+ = NotNullTypes columns
1027+
1028+foreignKey
1029+ :: ( HasTable reftable schema refcolumns
1030+ , SameTypes subcolumns refsubcolumns
1031+ , NotNullTypes subcolumns
1032+ , SListI subcolumns
1033+ , SListI refsubcolumns)
1034+ => NP (Column columns) subcolumns
1035+ -> Alias reftable
1036+ -> NP (Column refcolumns) refsubcolumns
1037+ -> TableConstraint schema columns
1038+foreignKey columns reftable refcolumns = UnsafeTableConstraint $
1039+ "FOREIGN KEY" <+> parenthesized (renderCommaSeparated renderColumn columns)
1040+ <+> "REFERENCES" <+> renderAlias reftable
1041+ <+> parenthesized (renderCommaSeparated renderColumn refcolumns)
1042+
1043+{-----------------------------------------
1044+DROP statements
1045+-----------------------------------------}
1046+
1047+dropTable
1048+ :: KnownSymbol table
1049+ => Alias table
1050+ -> Definition schema (Drop table schema)
1051+dropTable table = UnsafeDefinition $ "DROP TABLE" <+> renderAlias table <> ";"
1052+
1053+{-----------------------------------------
1054+ALTER statements
1055+-----------------------------------------}
1056+
1057+alterTable
1058+ :: HasTable table schema columns0
1059+ => Alias table
1060+ -> AlterTable columns0 columns1
1061+ -> Definition schema (Alter table schema columns1)
1062+alterTable table alteration = UnsafeDefinition $
1063+ "ALTER TABLE"
1064+ <+> renderAlias table
1065+ <+> renderAlterTable alteration
1066+ <> ";"
1067+
1068+alterTableRename
1069+ :: (KnownSymbol table0, KnownSymbol table1)
1070+ => Alias table0
1071+ -> Alias table1
1072+ -> Definition schema (Rename table0 table1 schema)
1073+alterTableRename table0 table1 = UnsafeDefinition $
1074+ "ALTER TABLE" <+> renderAlias table0
1075+ <+> "RENAME TO" <+> renderAlias table1 <> ";"
1076+
1077+data AlterTable
1078+ (columns0 :: [(Symbol, ColumnType)])
1079+ (columns1 :: [(Symbol, ColumnType)]) =
1080+ UnsafeAlterTable {renderAlterTable :: ByteString}
1081+
1082+addColumnDefault
1083+ :: KnownSymbol column
1084+ => Alias column
1085+ -> TypeExpression ('Optional ty)
1086+ -> AlterTable columns (Create column ('Optional ty) columns)
1087+addColumnDefault column ty = UnsafeAlterTable $
1088+ "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
1089+
1090+addColumnNull
1091+ :: KnownSymbol column
1092+ => Alias column
1093+ -> TypeExpression ('Required ('Null ty))
1094+ -> AlterTable columns ((column ::: 'Required ('Null ty)) ': columns)
1095+addColumnNull column ty = UnsafeAlterTable $
1096+ "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
1097+
1098+dropColumn
1099+ :: KnownSymbol column
1100+ => Alias column
1101+ -> AlterTable columns (Drop column columns)
1102+dropColumn column = UnsafeAlterTable $
1103+ "DROP COLUMN" <+> renderAlias column
1104+
1105+renameColumn
1106+ :: (KnownSymbol column0, KnownSymbol column1)
1107+ => Alias column0
1108+ -> Alias column1
1109+ -> AlterTable columns (Rename column0 column1 columns)
1110+renameColumn column0 column1 = UnsafeAlterTable $
1111+ "RENAME COLUMN" <+> renderAlias column0 <+> "TO" <+> renderAlias column1
1112+
1113+alterColumn
1114+ :: (KnownSymbol column, HasColumn column columns ty0)
1115+ => Alias column
1116+ -> AlterColumn ty0 ty1
1117+ -> AlterTable columns (Alter column columns ty1)
1118+alterColumn column alteration = UnsafeAlterTable $
1119+ "ALTER COLUMN" <+> renderAlias column <+> renderAlterColumn alteration
1120+
1121+data AlterColumn (ty0 :: ColumnType) (ty1 :: ColumnType) =
1122+ UnsafeAlterColumn {renderAlterColumn :: ByteString}
1123+
1124+setDefault
1125+ :: Expression '[] '[] 'Ungrouped ('Required ty)
1126+ -> AlterColumn ('Required ty) ('Optional ty)
1127+setDefault expression = UnsafeAlterColumn $
1128+ "SET DEFAULT" <+> renderExpression expression
1129+
1130+dropDefault :: AlterColumn ('Optional ty) ('Required ty)
1131+dropDefault = UnsafeAlterColumn $ "DROP DEFAULT"
1132+
1133+setNotNull
1134+ :: AlterColumn (optionality ('Null ty)) (optionality ('NotNull ty))
1135+setNotNull = UnsafeAlterColumn $ "SET NOT NULL"
1136+
1137+dropNotNull
1138+ :: AlterColumn (optionality ('NotNull ty)) (optionality ('Null ty))
1139+dropNotNull = UnsafeAlterColumn $ "DROP NOT NULL"
1140+
1141+alterType
1142+ :: TypeExpression (optionality (nullity ty1))
1143+ -> AlterColumn (optionality (nullity ty0)) (optionality (nullity ty1))
1144+alterType ty = UnsafeAlterColumn $ "TYPE " <> renderTypeExpression ty
1145+
1146+{-----------------------------------------
1147+INSERT statements
1148+-----------------------------------------}
1149+
1150+insertInto
1151+ :: (SListI columns, SListI results, HasTable table schema columns)
1152+ => Alias table
1153+ -> ValuesClause schema params columns
1154+ -> ConflictClause params columns
1155+ -> ReturningClause params columns results
1156+ -> Manipulation schema params results
1157+insertInto table insert conflict returning = UnsafeManipulation $
1158+ "INSERT" <+> "INTO" <+> renderAlias table
1159+ <+> renderValuesClause insert
1160+ <> renderConflictClause conflict
1161+ <> renderReturningClause returning
1162+
1163+data ValuesClause
1164+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
1165+ (params :: [ColumnType])
1166+ (columns :: [(Symbol,ColumnType)])
1167+ = Values
1168+ (NP (Aliased (Expression params '[] 'Ungrouped)) columns)
1169+ [NP (Aliased (Expression params '[] 'Ungrouped)) columns]
1170+ | ValuesQuery (Query schema params columns)
1171+
1172+renderValuesClause
1173+ :: SListI columns
1174+ => ValuesClause schema params columns
1175+ -> ByteString
1176+renderValuesClause = \case
1177+ Values row rows ->
1178+ parenthesized (renderCommaSeparated renderAliasPart row)
1179+ <+> "VALUES"
1180+ <+> commaSeparated
1181+ (parenthesized . renderCommaSeparated renderValuePart <$> row:rows)
1182+ where
1183+ renderAliasPart, renderValuePart
1184+ :: Aliased (Expression params '[] 'Ungrouped) ty -> ByteString
1185+ renderAliasPart (_ `As` name) = renderAlias name
1186+ renderValuePart (value `As` _) = renderExpression value
1187+ ValuesQuery q -> renderQuery q
1188+
1189+data ReturningClause
1190+ (params :: [ColumnType])
1191+ (columns :: [(Symbol,ColumnType)])
1192+ (results :: [(Symbol,ColumnType)])
1193+ where
1194+ ReturningStar :: ReturningClause params columns columns
1195+ Returning
1196+ :: NP (Aliased (Expression params '[table ::: columns] 'Ungrouped))
1197+ results
1198+ -> ReturningClause params columns results
1199+
1200+renderReturningClause
1201+ :: SListI results
1202+ => ReturningClause params columns results
1203+ -> ByteString
1204+renderReturningClause = \case
1205+ ReturningStar -> " RETURNING *;"
1206+ Returning Nil -> ";"
1207+ Returning results -> " RETURNING"
1208+ <+> renderCommaSeparated (renderAliased renderExpression) results <> ";"
1209+
1210+data ConflictClause params columns where
1211+ Conflict :: ConflictClause params columns
1212+ OnConflictDoNothing :: ConflictClause params columns
1213+ OnConflictDoUpdate
1214+ :: NP (Aliased (UpdateExpression params '[table ::: columns])) columns
1215+ -> Condition params '[table ::: columns] 'Ungrouped
1216+ -> ConflictClause params columns
1217+
1218+renderConflictClause
1219+ :: SListI columns
1220+ => ConflictClause params columns
1221+ -> ByteString
1222+renderConflictClause = \case
1223+ Conflict -> ""
1224+ OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
1225+ OnConflictDoUpdate updates wh
1226+ -> " ON CONFLICT DO UPDATE SET"
1227+ <+> renderCommaSeparatedMaybe renderUpdateExpression updates
1228+ <+> "WHERE" <+> renderExpression wh
1229+
1230+{-----------------------------------------
1231+UPDATE statements
1232+-----------------------------------------}
1233+
1234+data UpdateExpression params table ty
1235+ = Same
1236+ | Set (Expression params table 'Ungrouped ty)
1237+
1238+renderUpdateExpression
1239+ :: Aliased (UpdateExpression params '[table ::: columns]) column
1240+ -> Maybe ByteString
1241+renderUpdateExpression = \case
1242+ Same `As` _ -> Nothing
1243+ Set expression `As` column -> Just $
1244+ renderAlias column <+> "=" <+> renderExpression expression
1245+
1246+update
1247+ :: (HasTable table schema columns, SListI columns, SListI results)
1248+ => Alias table
1249+ -> NP (Aliased (UpdateExpression params '[table ::: columns])) columns
1250+ -> Condition params '[table ::: columns] 'Ungrouped
1251+ -> ReturningClause params columns results
1252+ -> Manipulation schema params results
1253+update table columns wh returning = UnsafeManipulation $
1254+ "UPDATE"
1255+ <+> renderAlias table
1256+ <+> "SET"
1257+ <+> renderCommaSeparatedMaybe renderUpdateExpression columns
1258+ <+> "WHERE" <+> renderExpression wh
1259+ <> renderReturningClause returning
1260+
1261+{-----------------------------------------
1262+DELETE statements
1263+-----------------------------------------}
1264+
1265+deleteFrom
1266+ :: HasTable table schema columns
1267+ => Alias table
1268+ -> Condition params '[table ::: columns] 'Ungrouped
1269+ -> Manipulation schema params '[]
1270+deleteFrom table wh = UnsafeManipulation $
1271+ "DELETE FROM" <+> renderAlias table
1272+ <+> "WHERE" <+> renderExpression wh <> ";"
1273+
1274+{-----------------------------------------
1275+render helper functions
1276+-----------------------------------------}
1277+
1278+parenthesized :: ByteString -> ByteString
1279+parenthesized str = "(" <> str <> ")"
1280+
1281+(<+>) :: ByteString -> ByteString -> ByteString
1282+str1 <+> str2 = str1 <> " " <> str2
1283+
1284+commaSeparated :: [ByteString] -> ByteString
1285+commaSeparated = ByteString.intercalate ", "
1286+
1287+renderCommaSeparated
1288+ :: SListI xs
1289+ => (forall x. expression x -> ByteString)
1290+ -> NP expression xs -> ByteString
1291+renderCommaSeparated render
1292+ = commaSeparated
1293+ . hcollapse
1294+ . hmap (K . render)
1295+
1296+renderCommaSeparatedMaybe
1297+ :: SListI xs
1298+ => (forall x. expression x -> Maybe ByteString)
1299+ -> NP expression xs -> ByteString
1300+renderCommaSeparatedMaybe render
1301+ = commaSeparated
1302+ . catMaybes
1303+ . hcollapse
1304+ . hmap (K . render)
1305+
1306+renderSymbol :: KnownSymbol str => Proxy# str -> ByteString
1307+renderSymbol p = fromString $ symbolVal' p
1308+
1309+renderNat :: KnownNat n => proxy n -> ByteString
1310+renderNat (_ :: proxy n) = fromString (show (natVal' (proxy# :: Proxy# n)))