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