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