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