@@ -0,0 +1,1148 @@
1+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
6+ , FunctionalDependencies
13+ , ScopedTypeVariables
17+ , UndecidableInstances
20+module Squeel.PostgreSQL.Statement where
22+import Control.Category
23+import Data.ByteString (ByteString)
32+import GHC.OverloadedLabels
34+import Prelude hiding (RealFrac(..), id, (.))
36+import qualified Data.ByteString as ByteString
38+import Squeel.PostgreSQL.Schema
40+{-----------------------------------------
42+-----------------------------------------}
45+ (params :: [ColumnType])
46+ (tables :: [(Symbol,[(Symbol,ColumnType)])])
47+ (grouping :: Grouping)
49+ = UnsafeExpression { renderExpression :: ByteString }
52+class KnownNat n => HasParameter (n :: Nat) params ty | n params -> ty where
53+ param :: Proxy# n -> Expression params tables grouping ty
54+ param p = UnsafeExpression $ ("$" <>) $ fromString $ show $ natVal' p
55+instance {-# OVERLAPPING #-} HasParameter 1 (ty1:tys) ty1
56+instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) params ty)
57+ => HasParameter n (ty' : params) ty
59+param1 :: Expression (ty1:tys) tables grouping ty1
60+param1 = param (proxy# :: Proxy# 1)
61+param2 :: Expression (ty1:ty2:tys) tables grouping ty2
62+param2 = param (proxy# :: Proxy# 2)
63+param3 :: Expression (ty1:ty2:ty3:tys) tables grouping ty3
64+param3 = param (proxy# :: Proxy# 3)
65+param4 :: Expression (ty1:ty2:ty3:ty4:tys) tables grouping ty4
66+param4 = param (proxy# :: Proxy# 4)
67+param5 :: Expression (ty1:ty2:ty3:ty4:ty5:tys) tables grouping ty5
68+param5 = param (proxy# :: Proxy# 5)
70+class KnownSymbol column => HasColumn column columns ty
71+ | column columns -> ty where
74+ -> Expression params '[table ::: columns] 'Ungrouped ty
75+ getColumn column = UnsafeExpression $ renderSymbol column
76+instance {-# OVERLAPPING #-} KnownSymbol column
77+ => HasColumn column ((column ::: optionality ty) ': tys) ('Required ty)
78+instance {-# OVERLAPPABLE #-} (KnownSymbol column, HasColumn column table ty)
79+ => HasColumn column (ty' ': table) ty
81+instance (HasColumn column columns ty, tables ~ '[table ::: columns])
82+ => IsLabel column (Expression params tables 'Ungrouped ty) where
83+ fromLabel = getColumn
85+instance (HasTable table tables columns, HasColumn column columns ty)
86+ => IsTableColumn table column (Expression params tables 'Ungrouped ty) where
87+ table &. column = UnsafeExpression $
88+ renderAlias table <> "." <> renderAlias column
90+def :: Expression params '[] grouping ('Optional (nullity ty))
91+def = UnsafeExpression "DEFAULT"
94+ :: Expression params '[] grouping ('Required (nullity ty))
95+ -> Expression params '[] grouping ('Optional (nullity ty))
96+unDef = UnsafeExpression . renderExpression
98+null :: Expression params tables grouping (optionality ('Null ty))
99+null = UnsafeExpression "NULL"
102+ :: Expression params tables grouping (optionality ('NotNull ty))
103+ -> Expression params tables grouping (optionality ('Null ty))
104+unNull = UnsafeExpression . renderExpression
107+ :: [Expression params tables grouping ('Required ('Null x))]
108+ -> Expression params tables grouping ('Required ('NotNull x))
109+ -> Expression params tables grouping ('Required ('NotNull x))
110+coalesce nulls isn'tNull = UnsafeExpression $
112+ <> parenthesized (commaSeparated (renderExpression <$> nulls))
113+ <> ", " <> renderExpression isn'tNull
117+ -> Expression params tables grouping ty0
118+ -> Expression params tables grouping ty1
119+ -> Expression params tables grouping ty2
120+unsafeBinaryOp op x y = UnsafeExpression $ parenthesized $
121+ renderExpression x <+> op <+> renderExpression y
125+ -> Expression params tables grouping ty0
126+ -> Expression params tables grouping ty1
127+unsafeUnaryOp op x = UnsafeExpression $ parenthesized $
128+ op <+> renderExpression x
132+ -> Expression params tables grouping xty
133+ -> Expression params tables grouping yty
134+unsafeFunction fun x = UnsafeExpression $
135+ fun <> parenthesized (renderExpression x)
138+ => Num (Expression params tables grouping ('Required (nullity ty))) where
139+ (+) = unsafeBinaryOp "+"
140+ (-) = unsafeBinaryOp "-"
141+ (*) = unsafeBinaryOp "*"
142+ abs = unsafeFunction "abs"
143+ signum = unsafeFunction "sign"
146+ . (<> if decimal (Proxy :: Proxy ty) then "." else "")
150+instance PGFractional ty => Fractional
151+ (Expression params tables grouping ('Required (nullity ty))) where
152+ (/) = unsafeBinaryOp "/"
153+ fromRational x = fromInteger (numerator x) / fromInteger (denominator x)
155+instance (PGFloating ty, PGCast 'PGNumeric ty, PGTyped ty) => Floating
156+ (Expression params tables grouping ('Required (nullity ty))) where
157+ pi = UnsafeExpression "pi()"
158+ exp = unsafeFunction "exp"
159+ log = unsafeFunction "ln"
160+ sqrt = unsafeFunction "sqrt"
161+ b ** x = UnsafeExpression $
162+ "power(" <> renderExpression b <> ", " <> renderExpression x <> ")"
163+ logBase b y = cast pgtype $ logBaseNumeric b y
166+ :: Expression params tables grouping ('Required (nullity ty))
167+ -> Expression params tables grouping ('Required (nullity ty))
168+ -> Expression params tables grouping ('Required (nullity 'PGNumeric))
169+ logBaseNumeric b' y' = UnsafeExpression $ mconcat
171+ , renderExpression b' <> "::numeric"
173+ , renderExpression y' <> "::numeric"
176+ sin = unsafeFunction "sin"
177+ cos = unsafeFunction "cos"
178+ tan = unsafeFunction "tan"
179+ asin = unsafeFunction "asin"
180+ acos = unsafeFunction "acos"
181+ atan = unsafeFunction "atan"
182+ sinh x = (exp x - exp (-x)) / 2
183+ cosh x = (exp x + exp (-x)) / 2
184+ tanh x = sinh x / cosh x
185+ asinh x = log (x + sqrt (x*x + 1))
186+ acosh x = log (x + sqrt (x*x - 1))
187+ atanh x = log ((1 + x) / (1 - x)) / 2
191+ => Expression params tables grouping ('Required (nullity float))
192+ -> Expression params tables grouping ('Required (nullity float))
193+ -> Expression params tables grouping ('Required (nullity float))
194+atan2 y x = UnsafeExpression $
195+ "atan2(" <> renderExpression y <> ", " <> renderExpression x <> ")"
197+class PGCast (ty0 :: PGType) (ty1 :: PGType) where
199+ :: TypeExpression ('Required ('Null ty1))
200+ -> Expression params tables grouping ('Required (nullity ty0))
201+ -> Expression params tables grouping ('Required (nullity ty1))
202+ cast ty x = UnsafeExpression $
203+ "(" <> renderExpression x <> "::" <> renderTypeExpression ty <> ")"
204+instance PGCast 'PGInt2 'PGInt2
205+instance PGCast 'PGInt2 'PGInt4
206+instance PGCast 'PGInt2 'PGInt8
207+instance PGCast 'PGInt4 'PGInt2
208+instance PGCast 'PGInt4 'PGInt4
209+instance PGCast 'PGInt4 'PGInt8
210+instance PGCast 'PGInt8 'PGInt2
211+instance PGCast 'PGInt8 'PGInt4
212+instance PGCast 'PGInt8 'PGInt8
213+instance PGCast 'PGInt8 'PGFloat4
214+instance PGCast 'PGInt8 'PGFloat8
215+instance PGCast 'PGInt8 'PGNumeric
218+ :: (PGNum ty, PGTyped ty, PGCast 'PGInt8 ty, PGCast ty 'PGInt8)
219+ => Expression params tables grouping ('Required (nullity ty))
220+ -> Expression params tables grouping ('Required (nullity ty))
221+ -> Expression params tables grouping ('Required (nullity ty))
222+div = unsafeBinaryOp "/"
225+ :: (PGNum ty, PGTyped ty, PGCast 'PGInt8 ty, PGCast ty 'PGInt8)
226+ => Expression params tables grouping ('Required (nullity ty))
227+ -> Expression params tables grouping ('Required (nullity ty))
228+ -> Expression params tables grouping ('Required (nullity ty))
229+mod = unsafeBinaryOp "%"
232+ :: (PGFractional frac, PGCast 'PGInt4 int, PGTyped int)
233+ => Expression params tables grouping ('Required (nullity frac))
234+ -> Expression params tables grouping ('Required (nullity int))
235+truncate = cast pgtype . truncate'
238+ :: Expression params tables grouping ('Required (nullity frac))
239+ -> Expression params tables grouping ('Required (nullity 'PGInt4))
240+ truncate' = unsafeFunction "trunc"
243+ :: (PGFractional frac, PGCast 'PGInt4 int, PGTyped int)
244+ => Expression params tables grouping ('Required (nullity frac))
245+ -> Expression params tables grouping ('Required (nullity int))
246+round = cast pgtype . round'
249+ :: Expression params tables grouping ('Required (nullity frac))
250+ -> Expression params tables grouping ('Required (nullity 'PGInt4))
251+ round' = unsafeFunction "round"
254+ :: (PGFractional frac, PGCast 'PGInt4 int, PGTyped int)
255+ => Expression params tables grouping ('Required (nullity frac))
256+ -> Expression params tables grouping ('Required (nullity int))
257+ceiling = cast pgtype . ceiling'
260+ :: Expression params tables grouping ('Required (nullity frac))
261+ -> Expression params tables grouping ('Required (nullity 'PGInt4))
262+ ceiling' = unsafeFunction "ceiling"
265+ (Expression params tables grouping ('Required (nullity 'PGText))) where
266+ fromString str = UnsafeExpression $
267+ "E\'" <> fromString (escape =<< str) <> "\'"
280+type Condition params tables grouping =
281+ Expression params tables grouping ('Required ('NotNull 'PGBool))
283+true :: Condition params tables grouping
284+true = UnsafeExpression "TRUE"
286+false :: Condition params tables grouping
287+false = UnsafeExpression "FALSE"
290+ :: Condition params tables grouping
291+ -> Condition params tables grouping
292+not_ = unsafeUnaryOp "NOT"
295+ :: Condition params tables grouping
296+ -> Condition params tables grouping
297+ -> Condition params tables grouping
298+(&&*) = unsafeBinaryOp "AND"
301+ :: Condition params tables grouping
302+ -> Condition params tables grouping
303+ -> Condition params tables grouping
304+(||*) = unsafeBinaryOp "OR"
307+ :: [(Condition params tables grouping, Expression params tables grouping ty)]
308+ -> Expression params tables grouping ty
309+ -> Expression params tables grouping ty
310+caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
314+ [ " WHEN ", renderExpression when_
315+ , " THEN ", renderExpression then_
317+ | (when_,then_) <- whenThens
319+ , " ELSE ", renderExpression else_
324+ :: Condition params tables grouping
325+ -> Expression params tables grouping ty
326+ -> Expression params tables grouping ty
327+ -> Expression params tables grouping ty
328+ifThenElse if_ then_ else_ = caseWhenThenElse [(if_,then_)] else_
331+ :: Expression params tables grouping ('Required (nullity ty))
332+ -> Expression params tables grouping ('Required (nullity ty))
333+ -> Expression params tables grouping ('Required (nullity 'PGBool))
334+(==*) = unsafeBinaryOp "="
337+ :: Expression params tables grouping ('Required (nullity ty))
338+ -> Expression params tables grouping ('Required (nullity ty))
339+ -> Expression params tables grouping ('Required (nullity 'PGBool))
340+(/=*) = unsafeBinaryOp "<>"
343+ :: Expression params tables grouping ('Required (nullity ty))
344+ -> Expression params tables grouping ('Required (nullity ty))
345+ -> Expression params tables grouping ('Required (nullity 'PGBool))
346+(>=*) = unsafeBinaryOp ">="
349+ :: Expression params tables grouping ('Required (nullity ty))
350+ -> Expression params tables grouping ('Required (nullity ty))
351+ -> Expression params tables grouping ('Required (nullity 'PGBool))
352+(<*) = unsafeBinaryOp "<"
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 "<="
361+ :: Expression params tables grouping ('Required (nullity ty))
362+ -> Expression params tables grouping ('Required (nullity ty))
363+ -> Expression params tables grouping ('Required (nullity 'PGBool))
364+(>*) = unsafeBinaryOp ">"
366+data SortExpression params tables grouping where
368+ :: Expression params tables grouping ty
369+ -> SortExpression params tables grouping
371+ :: Expression params tables grouping ty
372+ -> SortExpression params tables grouping
374+renderSortExpression :: SortExpression params tables grouping -> ByteString
375+renderSortExpression = \case
376+ Asc expression -> renderExpression expression <+> "ASC"
377+ Desc expression -> renderExpression expression <+> "DESC"
379+{-----------------------------------------
381+-----------------------------------------}
384+ (tables :: [(Symbol,[(Symbol,ColumnType)])])
385+ (tabcol :: (Symbol,Symbol)) where
387+ :: (HasTable table tables columns, HasColumn column columns ty)
388+ => (Alias table, Alias column)
389+ -> By tables '(table, column)
391+renderBy :: By tables tabcolty -> ByteString
392+renderBy (By (table, column)) = renderAlias table <> "." <> renderAlias column
394+data GroupByClause tables grouping where
395+ NoGroups :: GroupByClause tables 'Ungrouped
398+ => NP (By tables) bys
399+ -> GroupByClause tables ('Grouped bys)
401+renderGroupByClause :: GroupByClause tables grouping -> ByteString
402+renderGroupByClause = \case
405+ Group bys -> " GROUP BY" <+> renderCommaSeparated renderBy bys
407+data HavingClause params tables grouping where
408+ NoHaving :: HavingClause params tables 'Ungrouped
410+ :: [Condition params tables ('Grouped bys)]
411+ -> HavingClause params tables ('Grouped bys)
413+renderHavingClause :: HavingClause params tables grouping -> ByteString
414+renderHavingClause = \case
417+ Having conditions ->
418+ " HAVING" <+> commaSeparated (renderExpression <$> conditions)
420+class (KnownSymbol table, KnownSymbol column)
421+ => GroupedBy table column bys where
423+ :: (tables ~ '[table ::: columns], HasColumn column columns ty)
425+ -> Expression params tables ('Grouped bys) ty
426+ getGroup1 column = UnsafeExpression $ fromString (symbolVal' column)
428+ :: (HasTable table tables columns, HasColumn column columns ty)
431+ -> Expression params tables ('Grouped bys) ty
432+ getGroup2 table column = UnsafeExpression $
433+ renderAlias table <> "." <> renderAlias column
434+instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
435+ => GroupedBy table column ('(table,column) ': bys)
436+instance {-# OVERLAPPABLE #-}
438+ , KnownSymbol column
439+ , GroupedBy table column bys
440+ ) => GroupedBy table column (tabcol ': bys)
443+ ( tables ~ '[table ::: columns]
444+ , HasColumn column columns ty
445+ , GroupedBy table column bys
447+ (Expression params tables ('Grouped bys) ty) where fromLabel = getGroup1
450+ ( HasTable table tables columns
451+ , HasColumn column columns ty
452+ , GroupedBy table column bys
453+ ) => IsTableColumn table column
454+ (Expression params tables ('Grouped bys) ty) where (&.) = getGroup2
458+ -> Expression params tables 'Ungrouped xty
459+ -> Expression params tables ('Grouped bys) yty
460+unsafeAggregate fun x = UnsafeExpression $ mconcat
461+ [fun, "(", renderExpression x, ")"]
465+ => Expression params tables 'Ungrouped ('Required (nullity ty))
466+ -> Expression params tables ('Grouped bys) ('Required (nullity ty))
467+sum_ = unsafeAggregate "sum"
469+{-----------------------------------------
471+-----------------------------------------}
474+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
475+ (columns :: [(Symbol,ColumnType)])
476+ = UnsafeTable { renderTable :: ByteString }
479+class KnownSymbol table => HasTable table tables columns
480+ | table tables -> columns where
481+ getTable :: Proxy# table -> Table tables columns
482+ getTable table = UnsafeTable $ fromString $ symbolVal' table
483+instance {-# OVERLAPPING #-} KnownSymbol table
484+ => HasTable table ((table ::: columns) ': tables) columns
485+instance {-# OVERLAPPABLE #-}
486+ (KnownSymbol table, HasTable table schema columns)
487+ => HasTable table (table' ': schema) columns
489+instance HasTable table schema columns
490+ => IsLabel table (Table schema columns) where
491+ fromLabel = getTable
493+data TableReference params schema tables where
495+ :: Aliased (Table schema) table
496+ -> TableReference params schema '[table]
498+ :: Aliased (Query schema params) table
499+ -> TableReference params schema '[table]
501+ :: TableReference params schema right
502+ -> TableReference params schema left
503+ -> TableReference params schema (Join left right)
505+ :: TableReference params schema right
506+ -> Condition params (Join left right) 'Ungrouped
507+ -> TableReference params schema left
508+ -> TableReference params schema (Join left right)
510+ :: TableReference params schema right
511+ -> Condition params (Join left right) 'Ungrouped
512+ -> TableReference params schema left
513+ -> TableReference params schema (Join left (NullifyTables right))
515+ :: TableReference params schema right
516+ -> Condition params (Join left right) 'Ungrouped
517+ -> TableReference params schema left
518+ -> TableReference params schema (Join (NullifyTables left) right)
520+ :: TableReference params schema right
521+ -> Condition params (Join left right) 'Ungrouped
522+ -> TableReference params schema left
523+ -> TableReference params schema
524+ (Join (NullifyTables left) (NullifyTables right))
526+renderTableReference :: TableReference params schema tables -> ByteString
527+renderTableReference = \case
528+ Table table -> renderAliased renderTable table
529+ Subquery selection -> renderAliased renderQuery selection
530+ CrossJoin right left ->
531+ renderTableReference left <+> "CROSS JOIN" <+> renderTableReference right
532+ InnerJoin right on left -> renderJoin "INNER JOIN" right on left
533+ LeftOuterJoin right on left -> renderJoin "LEFT OUTER JOIN" right on left
534+ RightOuterJoin right on left -> renderJoin "RIGHT OUTER JOIN" right on left
535+ FullOuterJoin right on left -> renderJoin "FULL OUTER JOIN" right on left
537+ renderJoin op right on left =
538+ renderTableReference left <+> op <+> renderTableReference right
539+ <+> "ON" <+> renderExpression on
542+ (params :: [ColumnType])
543+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
544+ (tables :: [(Symbol,[(Symbol,ColumnType)])])
545+ (grouping :: Grouping)
547+ { fromClause :: TableReference params schema tables
548+ , whereClause :: [Condition params tables 'Ungrouped]
549+ , groupByClause :: GroupByClause tables grouping
550+ , havingClause :: HavingClause params tables grouping
551+ , orderByClause :: [SortExpression params tables grouping]
552+ , limitClause :: [Word64]
553+ , offsetClause :: [Word64]
556+renderTableExpression
557+ :: TableExpression params schema tables grouping
559+renderTableExpression
560+ (TableExpression tables whs' grps' hvs' srts' lims' offs') = mconcat
561+ [ "FROM ", renderTableReference tables
563+ , renderGroupByClause grps'
564+ , renderHavingClause hvs'
566+ , renderLimits lims'
567+ , renderOffsets offs'
570+ renderWheres = \case
572+ wh:[] -> " WHERE" <+> renderExpression wh
573+ wh:whs -> " WHERE" <+> renderExpression (foldr (&&*) wh whs)
577+ <+> commaSeparated (renderSortExpression <$> srts)
578+ renderLimits = \case
580+ lims -> " LIMIT" <+> fromString (show (minimum lims))
581+ renderOffsets = \case
583+ offs -> " OFFSET" <+> fromString (show (sum offs))
586+ :: TableReference params schema tables
587+ -> TableExpression params schema tables 'Ungrouped
588+from tables = TableExpression tables [] NoGroups NoHaving [] [] []
591+ :: Condition params tables 'Ungrouped
592+ -> TableExpression params schema tables grouping
593+ -> TableExpression params schema tables grouping
594+where_ wh tables = tables {whereClause = wh : whereClause tables}
598+ => NP (By tables) bys
599+ -> TableExpression params schema tables 'Ungrouped
600+ -> TableExpression params schema tables ('Grouped bys)
601+group bys tables = TableExpression
602+ { fromClause = fromClause tables
603+ , whereClause = whereClause tables
604+ , groupByClause = Group bys
605+ , havingClause = Having []
606+ , orderByClause = []
607+ , limitClause = limitClause tables
608+ , offsetClause = offsetClause tables
612+ :: Condition params tables ('Grouped bys)
613+ -> TableExpression params schema tables ('Grouped bys)
614+ -> TableExpression params schema tables ('Grouped bys)
615+having hv tables = tables
616+ { havingClause = case havingClause tables of Having hvs -> Having (hv:hvs) }
619+ :: [SortExpression params tables grouping]
620+ -> TableExpression params schema tables grouping
621+ -> TableExpression params schema tables grouping
622+orderBy srts tables = tables {orderByClause = orderByClause tables ++ srts}
626+ -> TableExpression params schema tables grouping
627+ -> TableExpression params schema tables grouping
628+limit lim tables = tables {limitClause = lim : limitClause tables}
632+ -> TableExpression params schema tables grouping
633+ -> TableExpression params schema tables grouping
634+offset off tables = tables {offsetClause = off : offsetClause tables}
636+{-----------------------------------------
638+-----------------------------------------}
640+newtype TypeExpression (ty :: ColumnType)
641+ = UnsafeTypeExpression { renderTypeExpression :: ByteString }
644+bool :: TypeExpression ('Required ('Null 'PGBool))
645+bool = UnsafeTypeExpression "bool"
646+int2 :: TypeExpression ('Required ('Null 'PGInt2))
647+int2 = UnsafeTypeExpression "int2"
648+smallint :: TypeExpression ('Required ('Null 'PGInt2))
649+smallint = UnsafeTypeExpression "smallint"
650+int4 :: TypeExpression ('Required ('Null 'PGInt4))
651+int4 = UnsafeTypeExpression "int4"
652+int :: TypeExpression ('Required ('Null 'PGInt4))
653+int = UnsafeTypeExpression "int"
654+integer :: TypeExpression ('Required ('Null 'PGInt4))
655+integer = UnsafeTypeExpression "integer"
656+int8 :: TypeExpression ('Required ('Null 'PGInt8))
657+int8 = UnsafeTypeExpression "int8"
658+bigint :: TypeExpression ('Required ('Null 'PGInt8))
659+bigint = UnsafeTypeExpression "bigint"
660+numeric :: TypeExpression ('Required ('Null 'PGNumeric))
661+numeric = UnsafeTypeExpression "numeric"
662+float4 :: TypeExpression ('Required ('Null 'PGFloat4))
663+float4 = UnsafeTypeExpression "float4"
664+real :: TypeExpression ('Required ('Null 'PGFloat4))
665+real = UnsafeTypeExpression "real"
666+float8 :: TypeExpression ('Required ('Null 'PGFloat8))
667+float8 = UnsafeTypeExpression "float8"
668+doublePrecision :: TypeExpression ('Required ('Null 'PGFloat8))
669+doublePrecision = UnsafeTypeExpression "double precision"
670+serial2 :: TypeExpression ('Optional ('NotNull 'PGInt2))
671+serial2 = UnsafeTypeExpression "serial2"
672+smallserial :: TypeExpression ('Optional ('NotNull 'PGInt2))
673+smallserial = UnsafeTypeExpression "smallserial"
674+serial4 :: TypeExpression ('Optional ('NotNull 'PGInt4))
675+serial4 = UnsafeTypeExpression "serial4"
676+serial :: TypeExpression ('Optional ('NotNull 'PGInt4))
677+serial = UnsafeTypeExpression "serial"
678+serial8 :: TypeExpression ('Optional ('NotNull 'PGInt8))
679+serial8 = UnsafeTypeExpression "serial8"
680+bigserial :: TypeExpression ('Optional ('NotNull 'PGInt8))
681+bigserial = UnsafeTypeExpression "bigserial"
682+money :: TypeExpression ('Required ('Null 'PGMoney))
683+money = UnsafeTypeExpression "money"
684+text :: TypeExpression ('Required ('Null 'PGText))
685+text = UnsafeTypeExpression "text"
689+ -> TypeExpression ('Required ('Null ('PGChar n)))
690+char (_ :: proxy n) = UnsafeTypeExpression $
691+ "char(" <> fromString (show (natVal' (proxy# :: Proxy# n))) <> ")"
695+ -> TypeExpression ('Required ('Null ('PGChar n)))
696+character (_ :: proxy n) = UnsafeTypeExpression $
697+ "character(" <> fromString (show (natVal' (proxy# :: Proxy# n))) <> ")"
701+ -> TypeExpression ('Required ('Null ('PGVarChar n)))
702+varchar (_ :: proxy n) = UnsafeTypeExpression $
703+ "varchar(" <> fromString (show (natVal' (proxy# :: Proxy# n))) <> ")"
707+ -> TypeExpression ('Required ('Null ('PGVarChar n)))
708+characterVarying (_ :: proxy n) = UnsafeTypeExpression $
709+ "character varying(" <> fromString (show (natVal' (proxy# :: Proxy# n))) <> ")"
710+bytea :: TypeExpression ('Required ('Null ('PGBytea)))
711+bytea = UnsafeTypeExpression "bytea"
712+timestamp :: TypeExpression ('Required ('Null ('PGTimestamp)))
713+timestamp = UnsafeTypeExpression "timestamp"
714+timestampWithTimeZone :: TypeExpression ('Required ('Null ('PGTimestampTZ)))
715+timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
716+date :: TypeExpression ('Required ('Null ('PGDate)))
717+date = UnsafeTypeExpression "date"
718+time :: TypeExpression ('Required ('Null ('PGTime)))
719+time = UnsafeTypeExpression "time"
720+timeWithTimeZone :: TypeExpression ('Required ('Null ('PGTimeTZ)))
721+timeWithTimeZone = UnsafeTypeExpression "time with time zone"
722+interval :: TypeExpression ('Required ('Null ('PGInterval)))
723+interval = UnsafeTypeExpression "interval"
724+uuid :: TypeExpression ('Required ('Null ('PGUuid)))
725+uuid = UnsafeTypeExpression "uuid"
726+json :: TypeExpression ('Required ('Null ('PGJson)))
727+json = UnsafeTypeExpression "json"
728+jsonb :: TypeExpression ('Required ('Null ('PGJsonb)))
729+jsonb = UnsafeTypeExpression "jsonb"
732+ :: TypeExpression (optionality ('Null ty))
733+ -> TypeExpression (optionality ('NotNull ty))
734+notNull ty = UnsafeTypeExpression $ renderTypeExpression ty <+> "NOT NULL"
736+ :: Expression '[] '[] 'Ungrouped ('Required ty)
737+ -> TypeExpression ('Required ty)
738+ -> TypeExpression ('Optional ty)
739+default_ x ty = UnsafeTypeExpression $
740+ renderTypeExpression ty <+> "DEFAULT" <+> renderExpression x
742+class PGTyped (ty :: PGType) where
743+ pgtype :: TypeExpression ('Required ('Null ty))
744+instance PGTyped 'PGBool where pgtype = bool
745+instance PGTyped 'PGInt2 where pgtype = int2
746+instance PGTyped 'PGInt4 where pgtype = int4
747+instance PGTyped 'PGInt8 where pgtype = int8
748+instance PGTyped 'PGNumeric where pgtype = numeric
749+instance PGTyped 'PGFloat4 where pgtype = float4
750+instance PGTyped 'PGFloat8 where pgtype = float8
751+instance PGTyped 'PGMoney where pgtype = money
752+instance PGTyped 'PGText where pgtype = text
753+instance KnownNat n => PGTyped ('PGChar n) where pgtype = char (Proxy @n)
754+instance KnownNat n => PGTyped ('PGVarChar n) where pgtype = varchar (Proxy @n)
755+instance PGTyped 'PGBytea where pgtype = bytea
756+instance PGTyped 'PGTimestamp where pgtype = timestamp
757+instance PGTyped 'PGTimestampTZ where pgtype = timestampWithTimeZone
758+instance PGTyped 'PGDate where pgtype = date
759+instance PGTyped 'PGTime where pgtype = time
760+instance PGTyped 'PGTimeTZ where pgtype = timeWithTimeZone
761+instance PGTyped 'PGInterval where pgtype = interval
762+instance PGTyped 'PGUuid where pgtype = uuid
763+instance PGTyped 'PGJson where pgtype = json
764+instance PGTyped 'PGJsonb where pgtype = jsonb
766+{-----------------------------------------
768+-----------------------------------------}
771+ (schema0 :: [(Symbol,[(Symbol,ColumnType)])])
772+ (schema1 :: [(Symbol,[(Symbol,ColumnType)])])
773+ = UnsafeDefinition { renderDefinition :: ByteString }
775+instance Category Definition where
776+ id = UnsafeDefinition ";"
777+ ddl1 . ddl0 = UnsafeDefinition $
778+ renderDefinition ddl0 <+> renderDefinition ddl1
781+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
782+ (params :: [ColumnType])
783+ (columns :: [(Symbol,ColumnType)])
784+ = UnsafeManipulation { renderManipulation :: ByteString }
787+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
788+ (params :: [ColumnType])
789+ (columns :: [(Symbol,ColumnType)])
790+ = UnsafeQuery { renderQuery :: ByteString }
793+ :: Query schema params columns
794+ -> Manipulation schema params columns
795+query q = UnsafeManipulation $ renderQuery q <> ";"
797+union, unionAll, intersect, intersectAll, except, exceptAll
798+ :: Query schema params columns
799+ -> Query schema params columns
800+ -> Query schema params columns
801+q1 `union` q2 = UnsafeQuery $
802+ parenthesized (renderQuery q1)
804+ <+> parenthesized (renderQuery q2)
805+q1 `unionAll` q2 = UnsafeQuery $
806+ parenthesized (renderQuery q1)
807+ <+> "UNION" <+> "ALL"
808+ <+> parenthesized (renderQuery q2)
809+q1 `intersect` q2 = UnsafeQuery $
810+ parenthesized (renderQuery q1)
812+ <+> parenthesized (renderQuery q2)
813+q1 `intersectAll` q2 = UnsafeQuery $
814+ parenthesized (renderQuery q1)
815+ <+> "INTERSECT" <+> "ALL"
816+ <+> parenthesized (renderQuery q2)
817+q1 `except` q2 = UnsafeQuery $
818+ parenthesized (renderQuery q1)
820+ <+> parenthesized (renderQuery q2)
821+q1 `exceptAll` q2 = UnsafeQuery $
822+ parenthesized (renderQuery q1)
823+ <+> "EXCEPT" <+> "ALL"
824+ <+> parenthesized (renderQuery q2)
826+{-----------------------------------------
828+-----------------------------------------}
832+ => NP (Aliased (Expression params tables grouping)) columns
833+ -> TableExpression params schema tables grouping
834+ -> Query schema params columns
835+select list tabs = UnsafeQuery $
837+ <+> renderCommaSeparated (renderAliased renderExpression) list
838+ <+> renderTableExpression tabs
841+ :: (tables ~ '[table ::: columns])
842+ => TableExpression params schema tables 'Ungrouped
843+ -> Query schema params columns
844+selectStar tabs = UnsafeQuery $ "SELECT" <+> "*" <+> renderTableExpression tabs
847+ :: HasTable table tables columns
849+ -> TableExpression params schema tables 'Ungrouped
850+ -> Query schema params columns
851+selectDotStar table tables = UnsafeQuery $
852+ "SELECT" <+> renderAlias table <> ".*" <+> renderTableExpression tables
856+ => NP (Aliased (Expression params '[] 'Ungrouped)) columns
857+ -> Query schema params columns
858+selectRow row = UnsafeQuery $
859+ "SELECT" <+> renderCommaSeparated (renderAliased renderExpression) row
861+{-----------------------------------------
863+-----------------------------------------}
866+ :: (KnownSymbol table, SListI columns)
868+ -> NP (Aliased TypeExpression) columns
869+ -> Definition schema (Create table columns schema)
870+createTable table columns = UnsafeDefinition $
871+ "CREATE TABLE" <+> renderAlias table
872+ <+> parenthesized (renderCommaSeparated renderColumn columns) <> ";"
874+ renderColumn :: Aliased TypeExpression x -> ByteString
875+ renderColumn (ty `As` column) =
876+ renderAlias column <+> renderTypeExpression ty
878+{-----------------------------------------
880+-----------------------------------------}
883+ :: KnownSymbol table
885+ -> Definition schema (Drop table schema)
886+dropTable table = UnsafeDefinition $ "DROP TABLE" <+> renderAlias table <> ";"
888+{-----------------------------------------
890+-----------------------------------------}
893+ :: HasTable table schema columns0
895+ -> AlterTable columns0 columns1
896+ -> Definition schema (Alter table schema columns1)
897+alterTable table alteration = UnsafeDefinition $
899+ <+> renderAlias table
900+ <+> renderAlterTable alteration
904+ :: (KnownSymbol table0, KnownSymbol table1)
907+ -> Definition schema (Rename table0 table1 schema)
908+alterTableRename table0 table1 = UnsafeDefinition $
909+ "ALTER TABLE" <+> renderAlias table0
910+ <+> "RENAME TO" <+> renderAlias table1 <> ";"
913+ (columns0 :: [(Symbol, ColumnType)])
914+ (columns1 :: [(Symbol, ColumnType)]) =
915+ UnsafeAlterTable {renderAlterTable :: ByteString}
918+ :: KnownSymbol column
920+ -> TypeExpression ('Optional ty)
921+ -> AlterTable columns (Create column ('Optional ty) columns)
922+addColumnDefault column ty = UnsafeAlterTable $
923+ "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
926+ :: KnownSymbol column
928+ -> TypeExpression ('Required ('Null ty))
929+ -> AlterTable columns ((column ::: 'Required ('Null ty)) ': columns)
930+addColumnNull column ty = UnsafeAlterTable $
931+ "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
934+ :: KnownSymbol column
936+ -> AlterTable columns (Drop column columns)
937+dropColumn column = UnsafeAlterTable $
938+ "DROP COLUMN" <+> renderAlias column
941+ :: (KnownSymbol column0, KnownSymbol column1)
944+ -> AlterTable columns (Rename column0 column1 columns)
945+renameColumn column0 column1 = UnsafeAlterTable $
946+ "RENAME COLUMN" <+> renderAlias column0 <+> "TO" <+> renderAlias column1
949+ :: (KnownSymbol column, HasColumn column columns ty0)
951+ -> AlterColumn ty0 ty1
952+ -> AlterTable columns (Alter column columns ty1)
953+alterColumn column alteration = UnsafeAlterTable $
954+ "ALTER COLUMN" <+> renderAlias column <+> renderAlterColumn alteration
956+data AlterColumn (ty0 :: ColumnType) (ty1 :: ColumnType) =
957+ UnsafeAlterColumn {renderAlterColumn :: ByteString}
960+ :: Expression '[] '[] 'Ungrouped ('Required ty)
961+ -> AlterColumn ('Required ty) ('Optional ty)
962+setDefault expression = UnsafeAlterColumn $
963+ "SET DEFAULT" <+> renderExpression expression
965+dropDefault :: AlterColumn ('Optional ty) ('Required ty)
966+dropDefault = UnsafeAlterColumn $ "DROP DEFAULT"
969+ :: AlterColumn (optionality ('Null ty)) (optionality ('NotNull ty))
970+setNotNull = UnsafeAlterColumn $ "SET NOT NULL"
973+ :: AlterColumn (optionality ('NotNull ty)) (optionality ('Null ty))
974+dropNotNull = UnsafeAlterColumn $ "DROP NOT NULL"
978+ => TypeExpression (optionality (nullity ty1))
979+ -> AlterColumn (optionality (nullity ty0)) (optionality (nullity ty1))
980+alterType ty = UnsafeAlterColumn $ "TYPE " <> renderTypeExpression ty
982+{-----------------------------------------
984+-----------------------------------------}
987+ :: (SListI columns, SListI results, HasTable table schema columns)
989+ -> InsertClause schema params columns
990+ -> ConflictClause params columns
991+ -> ReturningClause params columns results
992+ -> Manipulation schema params results
993+into table insert conflict returning = UnsafeManipulation $
994+ "INSERT" <+> "INTO" <+> renderAlias table
995+ <+> renderInsertClause insert
996+ <> renderConflictClause conflict
997+ <> renderReturningClause returning
1000+ (schema :: [(Symbol,[(Symbol,ColumnType)])])
1001+ (params :: [ColumnType])
1002+ (columns :: [(Symbol,ColumnType)])
1004+ (NP (Aliased (Expression params '[] 'Ungrouped)) columns)
1005+ [NP (Aliased (Expression params '[] 'Ungrouped)) columns]
1006+ | InsertQuery (Query schema params columns)
1009+ :: (NP (Aliased (Expression params '[] 'Ungrouped)) columns)
1010+ -> InsertClause schema params columns
1011+insertRow row = InsertValues row []
1015+ => InsertClause schema params columns
1017+renderInsertClause = \case
1018+ InsertValues row rows ->
1019+ parenthesized (renderCommaSeparated renderAliasPart row)
1022+ (parenthesized . renderCommaSeparated renderValuePart <$> row:rows)
1024+ renderAliasPart, renderValuePart
1025+ :: Aliased (Expression params '[] 'Ungrouped) ty -> ByteString
1026+ renderAliasPart (_ `As` name) = renderAlias name
1027+ renderValuePart (value `As` _) = renderExpression value
1028+ InsertQuery q -> renderQuery q
1030+data ReturningClause
1031+ (params :: [ColumnType])
1032+ (columns :: [(Symbol,ColumnType)])
1033+ (results :: [(Symbol,ColumnType)])
1035+ ReturningNil :: ReturningClause params columns '[]
1036+ ReturningStar :: ReturningClause params columns columns
1038+ :: NP (Aliased (Expression params '[table ::: columns] 'Ungrouped)) results
1039+ -> ReturningClause params columns results
1041+renderReturningClause
1043+ => ReturningClause params columns results
1045+renderReturningClause = \case
1046+ ReturningNil -> ";"
1047+ ReturningStar -> " RETURNING *;"
1048+ Returning results -> " RETURNING"
1049+ <+> renderCommaSeparated (renderAliased renderExpression) results <> ";"
1051+data ConflictClause params columns where
1052+ Conflict :: ConflictClause params columns
1053+ OnConflictDoNothing :: ConflictClause params columns
1055+ :: NP (Aliased (UpdateExpression params '[table ::: columns])) columns
1056+ -> Condition params '[table ::: columns] 'Ungrouped
1057+ -> ConflictClause params columns
1059+renderConflictClause
1061+ => ConflictClause params columns
1063+renderConflictClause = \case
1065+ OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
1066+ OnConflictDoUpdate updates wh
1067+ -> " ON CONFLICT DO UPDATE SET"
1068+ <+> renderCommaSeparatedMaybe renderUpdateExpression updates
1069+ <+> "WHERE" <+> renderExpression wh
1071+{-----------------------------------------
1073+-----------------------------------------}
1075+data UpdateExpression params table ty
1077+ | Set (Expression params table 'Ungrouped ty)
1079+renderUpdateExpression
1080+ :: Aliased (UpdateExpression params '[table ::: columns]) column
1081+ -> Maybe ByteString
1082+renderUpdateExpression = \case
1083+ Same `As` _ -> Nothing
1084+ Set expression `As` column -> Just $
1085+ renderAlias column <+> "=" <+> renderExpression expression
1088+ :: (HasTable table schema columns, SListI columns, SListI results)
1090+ -> NP (Aliased (UpdateExpression params '[table ::: columns])) columns
1091+ -> Condition params '[table ::: columns] 'Ungrouped
1092+ -> ReturningClause params columns results
1093+ -> Manipulation schema params results
1094+update table columns wh returning = UnsafeManipulation $
1096+ <+> renderAlias table
1098+ <+> renderCommaSeparatedMaybe renderUpdateExpression columns
1099+ <+> "WHERE" <+> renderExpression wh
1100+ <> renderReturningClause returning
1102+{-----------------------------------------
1104+-----------------------------------------}
1107+ :: HasTable table schema columns
1109+ -> Condition params '[table ::: columns] 'Ungrouped
1110+ -> Manipulation schema params '[]
1111+deleteFrom table wh = UnsafeManipulation $
1112+ "DELETE FROM" <+> renderAlias table
1113+ <+> "WHERE" <+> renderExpression wh <> ";"
1115+{-----------------------------------------
1116+render helper functions
1117+-----------------------------------------}
1119+parenthesized :: ByteString -> ByteString
1120+parenthesized str = "(" <> str <> ")"
1122+(<+>) :: ByteString -> ByteString -> ByteString
1123+str1 <+> str2 = str1 <> " " <> str2
1125+commaSeparated :: [ByteString] -> ByteString
1126+commaSeparated = ByteString.intercalate ", "
1128+renderCommaSeparated
1130+ => (forall x. expression x -> ByteString)
1131+ -> NP expression xs -> ByteString
1132+renderCommaSeparated render
1135+ . hmap (K . render)
1137+renderCommaSeparatedMaybe
1139+ => (forall x. expression x -> Maybe ByteString)
1140+ -> NP expression xs -> ByteString
1141+renderCommaSeparatedMaybe render
1145+ . hmap (K . render)
1147+renderSymbol :: KnownSymbol str => Proxy# str -> ByteString
1148+renderSymbol p = fromString $ symbolVal' p