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