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