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