on dev help sign in
Merge pull request #44 from echatav/docs

Docs

commit535318213301e056db03c146e844666e38b5c6b0 authorEitan Chatav <eitan.chatav@gmail.com> dateSat 19 Aug 2017, 13:25 (9 years ago) parent2a737b6fd3b67a
17 files changed+2635−1914
squeal-postgresql/exe/Example.hs+11−15 squeal-postgresql/squeal-postgresql.cabal+18−2 squeal-postgresql/src/Squeal/PostgreSQL.hs+8−2 squeal-postgresql/src/Squeal/PostgreSQL/Binary.hs+120−38 squeal-postgresql/src/Squeal/PostgreSQL/Definition.hs+275 squeal-postgresql/src/Squeal/PostgreSQL/Expression.hs+905 squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hs+189 squeal-postgresql/src/Squeal/PostgreSQL/PQ.hs+24−8 squeal-postgresql/src/Squeal/PostgreSQL/Prettyprint.hs+50 squeal-postgresql/src/Squeal/PostgreSQL/Query.hs+393 squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+197−72 squeal-postgresql/src/Squeal/PostgreSQL/Statement.hs−1447 squeal-postgresql/test/DocTest.hs+12 squeal-postgresql/test/Squeal/PostgreSQL/DefinitionSpec.hs+124 squeal-postgresql/test/Squeal/PostgreSQL/ManipulationSpec.hs+128 squeal-postgresql/test/Squeal/PostgreSQL/QuerySpec.hs+181 squeal-postgresql/test/Squeal/PostgreSQL/StatementSpec.hs−330
squeal-postgresql/exe/Example.hs+11−15
@@ -1,4 +1,4 @@
1−{-# OPTIONS_GHC -fno-warn-unused-imports #-}
1+{-# OPTIONS_GHC -fno-warn-partial-type-signatures #-}
22{-# LANGUAGE
33 DataKinds
44 , DeriveGeneric
@@ -6,6 +6,7 @@
66 , MultiParamTypeClasses
77 , OverloadedLabels
88 , OverloadedStrings
9+ , PartialTypeSignatures
910 , TypeApplications
1011 , TypeOperators
1112 , TypeSynonymInstances
@@ -32,10 +33,10 @@ type Schema =
3233 ]
3334 ]
3435
35−data Table1Row = Table1Row { col1 :: Int32, col2 :: Int32 }
36+data Row = Row { col1 :: Int32, col2 :: Int32 }
3637 deriving (Show, GHC.Generic)
37−instance Generic Table1Row
38−instance HasDatatypeInfo Table1Row
38+instance Generic Row
39+instance HasDatatypeInfo Row
3940
4041main :: IO ()
4142main = do
@@ -52,17 +53,12 @@ main = do
5253 ((int4 & notNull) `As` #col1 :* (int4 & notNull) `As` #col2 :* Nil) []
5354 connection2 <- flip execPQ (connection1 :: Connection Schema) $ do
5455 let
55− insert :: Manipulation Schema
56− '[ 'Required ('NotNull 'PGint4)
57− , 'Required ('NotNull 'PGint4)
58− , 'Required ('NotNull 'PGint4)
59− , 'Required ('NotNull 'PGint4)
60− ] '[]
56+ insert :: Manipulation Schema '[_,_,_,_] '[]
6157 insert =
6258 insertInto #table1
6359 ( Values
64− (param (Proxy @1) `As` #col1 :* param (Proxy @2) `As` #col2 :* Nil)
65− [param (Proxy @3) `As` #col1 :* param (Proxy @4) `As` #col2 :* Nil]
60+ (param @1 `As` #col1 :* param @2 `As` #col2 :* Nil)
61+ [param @3 `As` #col1 :* param @4 `As` #col2 :* Nil]
6662 ) Conflict (Returning Nil)
6763 liftBase $ Char8.putStrLn "manipulating"
6864 _insertResult <- manipulateParams insert
@@ -82,9 +78,9 @@ main = do
8278 print (value01 :: Int32)
8379 print (value10 :: Int32)
8480 print (value11 :: Int32)
85− print (row0 :: Table1Row)
86− print (row1 :: Table1Row)
87− print (rows :: [Table1Row])
81+ print (row0 :: Row)
82+ print (row1 :: Row)
83+ print (rows :: [Row])
8884 Char8.putStrLn "tearing down schema"
8985 connection3 <- flip execPQ (connection2 :: Connection Schema) $ define $
9086 dropTable #table1 >>> dropTable #students
squeal-postgresql/squeal-postgresql.cabal+18−2
@@ -15,8 +15,12 @@ library
1515 exposed-modules:
1616 Squeal.PostgreSQL
1717 Squeal.PostgreSQL.Binary
18+ Squeal.PostgreSQL.Definition
19+ Squeal.PostgreSQL.Expression
20+ Squeal.PostgreSQL.Manipulation
1821 Squeal.PostgreSQL.PQ
19− Squeal.PostgreSQL.Statement
22+ Squeal.PostgreSQL.Prettyprint
23+ Squeal.PostgreSQL.Query
2024 Squeal.PostgreSQL.Schema
2125 default-language: Haskell2010
2226 ghc-options: -Wall -Werror -fprint-explicit-kinds
@@ -46,13 +50,25 @@ test-suite squeal-postgresql-test
4650 ghc-options: -Wall -Werror
4751 main-is: Spec.hs
4852 other-modules:
49− Squeal.PostgreSQL.StatementSpec
53+ Squeal.PostgreSQL.DefinitionSpec
54+ Squeal.PostgreSQL.ManipulationSpec
55+ Squeal.PostgreSQL.QuerySpec
5056 build-depends:
5157 base
5258 , generics-sop
5359 , hspec
5460 , squeal-postgresql
5561
62+test-suite squeal-postgresql-doctest
63+ default-language: Haskell2010
64+ type: exitcode-stdio-1.0
65+ hs-source-dirs: test
66+ ghc-options: -Wall -Werror
67+ main-is: DocTest.hs
68+ build-depends:
69+ base
70+ , doctest
71+
5672executable squeal-postgresql-example
5773 default-language: Haskell2010
5874 hs-source-dirs: exe
squeal-postgresql/src/Squeal/PostgreSQL.hs+8−2
@@ -1,11 +1,17 @@
11module Squeal.PostgreSQL
22 ( module Squeal.PostgreSQL.Binary
3+ , module Squeal.PostgreSQL.Definition
4+ , module Squeal.PostgreSQL.Expression
5+ , module Squeal.PostgreSQL.Manipulation
36 , module Squeal.PostgreSQL.PQ
7+ , module Squeal.PostgreSQL.Query
48 , module Squeal.PostgreSQL.Schema
5− , module Squeal.PostgreSQL.Statement
69 ) where
710
811import Squeal.PostgreSQL.Binary
12+import Squeal.PostgreSQL.Definition
13+import Squeal.PostgreSQL.Expression
14+import Squeal.PostgreSQL.Manipulation
915import Squeal.PostgreSQL.PQ
16+import Squeal.PostgreSQL.Query
1017import Squeal.PostgreSQL.Schema
11−import Squeal.PostgreSQL.Statement
squeal-postgresql/src/Squeal/PostgreSQL/Binary.hs+120−38
@@ -1,5 +1,16 @@
1+{-|
2+Module: Squeal.PostgreSQL.Binary
3+Description: Binary encoding and decoding
4+Copyright: (c) Eitan Chatav, 2017
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Binary encoding and decoding between Haskell and PostgreSQL types.
9+-}
10+
111{-# LANGUAGE
2− DataKinds
12+ ConstraintKinds
13+ , DataKinds
314 , DefaultSignatures
415 , DeriveFoldable
516 , DeriveFunctor
@@ -14,34 +25,64 @@
1425 , ScopedTypeVariables
1526 , TypeApplications
1627 , TypeFamilies
28+ , TypeInType
1729 , TypeOperators
1830 , UndecidableInstances
1931#-}
2032
21−module Squeal.PostgreSQL.Binary where
33+module Squeal.PostgreSQL.Binary
34+ ( -- * Encoding
35+ ToParam (..)
36+ , ToColumnParam (..)
37+ , ToParams (..)
38+ -- * Decoding
39+ , FromValue (..)
40+ , FromColumnValue (..)
41+ , FromRow (..)
42+ -- * Only
43+ , Only (..)
44+ ) where
2245
2346import Data.Aeson hiding (Null)
24−import Data.Bits
2547import Data.Int
48+import Data.Kind
2649import Data.Scientific
2750import Data.Time
28−import Data.Word
2951import Data.UUID
52+import Data.Word
3053import Generics.SOP
54+import GHC.TypeLits
3155import Network.IP.Addr
3256
3357import qualified Data.ByteString.Lazy as Lazy
3458import qualified Data.ByteString as Strict hiding (unpack)
3559import qualified Data.Text.Lazy as Lazy
3660import qualified Data.Text as Strict
37−import qualified Generics.SOP.Type.Metadata as Type
3861import qualified GHC.Generics as GHC
3962import qualified PostgreSQL.Binary.Decoding as Decoding
4063import qualified PostgreSQL.Binary.Encoding as Encoding
4164
4265import Squeal.PostgreSQL.Schema
4366
44−class ToParam x (pg :: PGType) where toParam :: x -> K Encoding.Encoding pg
67+-- | A `ToParam` constraint gives an encoding of a Haskell `Type` into
68+-- into the binary format of a PostgreSQL `PGType`.
69+class ToParam (x :: Type) (pg :: PGType) where
70+ -- | >>> :set -XTypeApplications -XDataKinds
71+ -- >>> toParam @Bool @'PGbool False
72+ -- K "\NUL"
73+ --
74+ -- >>> toParam @Int16 @'PGint2 0
75+ -- K "\NUL\NUL"
76+ --
77+ -- >>> toParam @Int32 @'PGint4 0
78+ -- K "\NUL\NUL\NUL\NUL"
79+ --
80+ -- >>> :set -XMultiParamTypeClasses
81+ -- >>> newtype Id = Id { getId :: Int16 } deriving Show
82+ -- >>> instance ToParam Id 'PGint2 where toParam = toParam . getId
83+ -- >>> toParam @Id @'PGint2 (Id 1)
84+ -- K "\NUL\SOH"
85+ toParam :: x -> K Encoding.Encoding pg
4586instance ToParam Bool 'PGbool where toParam = K . Encoding.bool
4687instance ToParam Int16 'PGint2 where toParam = K . Encoding.int2_int16
4788instance ToParam Word16 'PGint2 where toParam = K . Encoding.int2_word16
@@ -73,14 +114,35 @@ instance ToParam DiffTime 'PGinterval where toParam = K . Encoding.interval_int
73114instance ToParam Value 'PGjson where toParam = K . Encoding.json_ast
74115instance ToParam Value 'PGjsonb where toParam = K . Encoding.jsonb_ast
75116
76−class ToColumnParam x (ty :: ColumnType) where
117+-- | A `ToColumnParam` constraint lifts the `ToParam` encoding
118+-- of a `Type` to a `ColumnType`, encoding `Maybe`s to `Null`s.
119+class ToColumnParam (x :: Type) (ty :: ColumnType) where
120+ -- | >>> toColumnParam @Int16 @('Required ('NotNull 'PGint2)) 0
121+ -- K (Just "\NUL\NUL")
122+ --
123+ -- >>> toColumnParam @(Maybe Int16) @('Required ('Null 'PGint2)) (Just 0)
124+ -- K (Just "\NUL\NUL")
125+ --
126+ -- >>> toColumnParam @(Maybe Int16) @('Required ('Null 'PGint2)) Nothing
127+ -- K Nothing
77128 toColumnParam :: x -> K (Maybe Strict.ByteString) ty
78129instance ToParam x pg => ToColumnParam x (optionality ('NotNull pg)) where
79130 toColumnParam = K . Just . Encoding.encodingBytes . unK . toParam @x @pg
80131instance ToParam x pg => ToColumnParam (Maybe x) (optionality ('Null pg)) where
81132 toColumnParam = K . fmap (Encoding.encodingBytes . unK . toParam @x @pg)
82133
83−class SListI tys => ToParams x (tys :: [ColumnType]) where
134+-- | A `ToParams` constraint generically sequences the encodings of `Type`s
135+-- of the fields of a tuple or record to a row of `ColumnType`s.
136+class SListI tys => ToParams (x :: Type) (tys :: [ColumnType]) where
137+ -- | >>> type PGparams = '[ 'Required ('NotNull 'PGbool), 'Required ('Null 'PGint2)]
138+ -- >>> toParams @(Bool, Maybe Int16) @PGparams (False, Just 0)
139+ -- K (Just "\NUL") :* (K (Just "\NUL\NUL") :* Nil)
140+ --
141+ -- >>> :set -XDeriveGeneric
142+ -- >>> data Hparams = Hparams { col1 :: Bool, col2 :: Maybe Int16} deriving GHC.Generic
143+ -- >>> instance Generic Hparams
144+ -- >>> toParams @Hparams @PGparams (Hparams False (Just 0))
145+ -- K (Just "\NUL") :* (K (Just "\NUL\NUL") :* Nil)
84146 toParams :: x -> NP (K (Maybe Strict.ByteString)) tys
85147instance (SListI tys, IsProductType x xs, AllZip ToColumnParam xs tys)
86148 => ToParams x tys where
@@ -88,15 +150,16 @@ instance (SListI tys, IsProductType x xs, AllZip ToColumnParam xs tys)
88150 = htrans (Proxy @ToColumnParam) (toColumnParam . unI)
89151 . unZ . unSOP . from
90152
91−class FromValue (pg :: PGType) y where
153+-- | A `FromValue` constraint gives a parser from the binary format of
154+-- a PostgreSQL `PGType` into a Haskell `Type`.
155+class FromValue (pg :: PGType) (y :: Type) where
156+ -- | >>> newtype Id = Id { getId :: Int16 } deriving Show
157+ -- >>> instance FromValue 'PGint2 Id where fromValue = fmap Id . fromValue
92158 fromValue :: proxy pg -> Decoding.Value y
93159instance FromValue 'PGbool Bool where fromValue _ = Decoding.bool
94−instance (Integral int, Bits int) => FromValue 'PGint2 int where
95− fromValue _ = Decoding.int
96−instance (Integral int, Bits int) => FromValue 'PGint4 int where
97− fromValue _ = Decoding.int
98−instance (Integral int, Bits int) => FromValue 'PGint8 int where
99− fromValue _ = Decoding.int
160+instance FromValue 'PGint2 Int16 where fromValue _ = Decoding.int
161+instance FromValue 'PGint4 Int32 where fromValue _ = Decoding.int
162+instance FromValue 'PGint8 Int64 where fromValue _ = Decoding.int
100163instance FromValue 'PGfloat4 Float where fromValue _ = Decoding.float4
101164instance FromValue 'PGfloat8 Double where fromValue _ = Decoding.float8
102165instance FromValue 'PGnumeric Scientific where fromValue _ = Decoding.numeric
@@ -122,7 +185,18 @@ instance FromValue 'PGinterval DiffTime where
122185instance FromValue 'PGjson Value where fromValue _ = Decoding.json_ast
123186instance FromValue 'PGjsonb Value where fromValue _ = Decoding.jsonb_ast
124187
125−class FromColumnValue (colty :: (Symbol,ColumnType)) y where
188+-- | A `FromColumnValue` constraint lifts the `FromValue` parser
189+-- to a decoding of a `(Symbol,ColumnType)` to a `Type`,
190+-- decoding `Null`s to `Maybe`s.
191+class FromColumnValue (colty :: (Symbol,ColumnType)) (y :: Type) where
192+ -- | >>> :set -XTypeOperators -XOverloadedStrings
193+ -- >>> newtype Id = Id { getId :: Int16 } deriving Show
194+ -- >>> instance FromValue 'PGint2 Id where fromValue = fmap Id . fromValue
195+ -- >>> fromColumnValue @("col" ::: 'Required ('NotNull 'PGint2)) @Id (K (Just "\NUL\SOH"))
196+ -- Id {getId = 1}
197+ --
198+ -- >>> fromColumnValue @("col" ::: 'Required ('Null 'PGint2)) @(Maybe Id) (K (Just "\NUL\SOH"))
199+ -- Just (Id {getId = 1})
126200 fromColumnValue :: K (Maybe Strict.ByteString) colty -> y
127201instance FromValue pg y
128202 => FromColumnValue (column ::: ('Required ('NotNull pg))) y where
@@ -144,32 +218,40 @@ instance FromValue pg y
144218 where
145219 err str = error $ "fromColumnValue: " ++ Strict.unpack str
146220
147−class SListI columns => FromRow (columns :: [(Symbol,ColumnType)]) y where
148− fromRow :: NP (K (Maybe Strict.ByteString)) columns -> y
221+-- | A `FromRow` constraint generically sequences the parsings of the columns
222+-- of a `ColumnsType` into the fields of a record `Type` provided they have
223+-- the same field names.
224+class SListI results => FromRow (results :: ColumnsType) y where
225+ -- | >>> :set -XOverloadedStrings
226+ -- >>> import Data.Text
227+ -- >>> newtype Id = Id { getId :: Int16 } deriving Show
228+ -- >>> instance FromValue 'PGint2 Id where fromValue = fmap Id . fromValue
229+ -- >>> data Hrow = Hrow { userId :: Id, userName :: Maybe Text } deriving (Show, GHC.Generic)
230+ -- >>> instance Generic Hrow
231+ -- >>> instance HasDatatypeInfo Hrow
232+ -- >>> type PGrow = '["userId" ::: 'Required ('NotNull 'PGint2), "userName" ::: 'Required ('Null 'PGtext)]
233+ -- >>> fromRow @PGrow @Hrow (K (Just "\NUL\SOH") :* K (Just "bloodninja") :* Nil)
234+ -- Hrow {userId = Id {getId = 1}, userName = Just "bloodninja"}
235+ fromRow :: NP (K (Maybe Strict.ByteString)) results -> y
149236instance
150− ( SListI columns, IsProductType y ys
151− , AllZip FromColumnValue columns ys
152− , HasDatatypeInfo y
153− , SameFields (DatatypeInfoOf y) columns
154− ) => FromRow columns y where
237+ ( SListI results
238+ , IsProductType y ys
239+ , AllZip FromColumnValue results ys
240+ , SameFields (DatatypeInfoOf y) results
241+ ) => FromRow results y where
155242 fromRow
156243 = to . SOP . Z . htrans (Proxy @FromColumnValue) (I . fromColumnValue)
157244
158−class SameField
159− (fieldInfo :: Type.FieldInfo) (columnty :: (Symbol,ColumnType)) where
160−instance field ~ column => SameField ('Type.FieldInfo field) (column ::: ty)
161−
162−type family SameFields
163− (datatypeInfo :: Type.DatatypeInfo) (columns :: [(Symbol,ColumnType)]) where
164− SameFields
165− ('Type.ADT _module _datatype '[ 'Type.Record _constructor fields])
166− columns
167− = AllZip SameField fields columns
168− SameFields
169− ('Type.Newtype _module _datatype ('Type.Record _constructor '[field]))
170− '[column]
171− = SameField field column
172−
245+-- | `Only` is a 1-tuple type, useful for encoding a single parameter with
246+-- `toParams` or decoding a single value with `fromRow`.
247+--
248+-- >>> import Data.Text
249+-- >>> toParams @(Only (Maybe Text)) @'[ 'Required ('Null 'PGtext)] (Only (Just "foo"))
250+-- K (Just "foo") :* Nil
251+--
252+-- >>> type PGShortRow = '["only" ::: 'Required ('Null 'PGtext)]
253+-- >>> fromRow @PGShortRow @(Only (Maybe Text)) (K (Just "bar") :* Nil)
254+-- Only {only = Just "bar"}
173255newtype Only x = Only { only :: x }
174256 deriving (Functor,Foldable,Traversable,Eq,Ord,Read,Show,GHC.Generic)
175257instance Generic (Only x)
squeal-postgresql/src/Squeal/PostgreSQL/Definition.hsnew file+275
@@ -0,0 +1,275 @@
1+{-# LANGUAGE
2+ DataKinds
3+ , DeriveDataTypeable
4+ , DeriveGeneric
5+ , GADTs
6+ , GeneralizedNewtypeDeriving
7+ , KindSignatures
8+ , LambdaCase
9+ , OverloadedStrings
10+ , RankNTypes
11+ , StandaloneDeriving
12+ , TypeInType
13+ , TypeOperators
14+#-}
15+
16+module Squeal.PostgreSQL.Definition
17+ ( -- * Data Definition Language
18+ Definition (UnsafeDefinition, renderDefinition)
19+ , createTable
20+ , TableConstraint (UnsafeTableConstraint, renderTableConstraint)
21+ , check
22+ , unique
23+ , primaryKey
24+ , foreignKey
25+ , OnDelete (OnDeleteNoAction, OnDeleteRestrict, OnDeleteCascade)
26+ , renderOnDelete
27+ , OnUpdate (OnUpdateNoAction, OnUpdateRestrict, OnUpdateCascade)
28+ , renderOnUpdate
29+ , dropTable
30+ , alterTable
31+ , alterTableRename
32+ , AlterTable (UnsafeAlterTable, renderAlterTable)
33+ , addColumnDefault
34+ , addColumnNull
35+ , dropColumn
36+ , renameColumn
37+ , alterColumn
38+ , AlterColumn (UnsafeAlterColumn, renderAlterColumn)
39+ , setDefault
40+ , dropDefault
41+ , setNotNull
42+ , dropNotNull
43+ , alterType
44+ ) where
45+
46+import Control.Category
47+import Control.DeepSeq
48+import Data.ByteString
49+import Data.Monoid
50+import Generics.SOP
51+import GHC.TypeLits
52+import Prelude hiding ((.), id)
53+
54+import qualified GHC.Generics as GHC
55+
56+import Squeal.PostgreSQL.Expression
57+import Squeal.PostgreSQL.Prettyprint
58+import Squeal.PostgreSQL.Schema
59+
60+{-----------------------------------------
61+statements
62+-----------------------------------------}
63+
64+newtype Definition
65+ (schema0 :: TablesType)
66+ (schema1 :: TablesType)
67+ = UnsafeDefinition { renderDefinition :: ByteString }
68+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
69+
70+instance Category Definition where
71+ id = UnsafeDefinition ";"
72+ ddl1 . ddl0 = UnsafeDefinition $
73+ renderDefinition ddl0 <+> renderDefinition ddl1
74+
75+{-----------------------------------------
76+CREATE statements
77+-----------------------------------------}
78+
79+createTable
80+ :: (KnownSymbol table, SListI columns)
81+ => Alias table
82+ -> NP (Aliased TypeExpression) (column ': columns)
83+ -> [TableConstraint schema (column ': columns)]
84+ -> Definition schema (Create table (column ': columns) schema)
85+createTable table columns constraints = UnsafeDefinition $
86+ "CREATE TABLE" <+> renderAlias table
87+ <+> parenthesized
88+ ( renderCommaSeparated renderColumnDef columns
89+ <> renderConstraints constraints )
90+ <> ";"
91+ where
92+ renderColumnDef :: Aliased TypeExpression x -> ByteString
93+ renderColumnDef (ty `As` column) =
94+ renderAlias column <+> renderTypeExpression ty
95+ renderConstraints :: [TableConstraint schema columns] -> ByteString
96+ renderConstraints = \case
97+ [] -> ""
98+ _ -> ", " <> commaSeparated (renderTableConstraint <$> constraints)
99+
100+newtype TableConstraint
101+ (schema :: TablesType)
102+ (columns :: ColumnsType)
103+ = UnsafeTableConstraint { renderTableConstraint :: ByteString }
104+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
105+
106+check
107+ :: Condition '[table ::: columns] 'Ungrouped '[]
108+ -> TableConstraint schema columns
109+check condition = UnsafeTableConstraint $
110+ "CHECK" <+> parenthesized (renderExpression condition)
111+
112+unique
113+ :: SListI subcolumns
114+ => NP (Column columns) subcolumns
115+ -> TableConstraint schema columns
116+unique columns = UnsafeTableConstraint $
117+ "UNIQUE" <+> parenthesized (renderCommaSeparated renderColumn columns)
118+
119+primaryKey
120+ :: (SListI subcolumns, AllNotNull subcolumns)
121+ => NP (Column columns) subcolumns
122+ -> TableConstraint schema columns
123+primaryKey columns = UnsafeTableConstraint $
124+ "PRIMARY KEY" <+> parenthesized (renderCommaSeparated renderColumn columns)
125+
126+foreignKey
127+ :: ( HasTable reftable schema refcolumns
128+ , SameTypes subcolumns refsubcolumns
129+ , NotAllNull subcolumns
130+ , SListI subcolumns
131+ , SListI refsubcolumns)
132+ => NP (Column columns) subcolumns
133+ -> Alias reftable
134+ -> NP (Column refcolumns) refsubcolumns
135+ -> OnDelete
136+ -> OnUpdate
137+ -> TableConstraint schema columns
138+foreignKey columns reftable refcolumns onDelete onUpdate =
139+ UnsafeTableConstraint $
140+ "FOREIGN KEY" <+> parenthesized (renderCommaSeparated renderColumn columns)
141+ <+> "REFERENCES" <+> renderAlias reftable
142+ <+> parenthesized (renderCommaSeparated renderColumn refcolumns)
143+ <+> renderOnDelete onDelete
144+ <+> renderOnUpdate onUpdate
145+
146+data OnDelete
147+ = OnDeleteNoAction
148+ | OnDeleteRestrict
149+ | OnDeleteCascade
150+ deriving (GHC.Generic,Show,Eq,Ord)
151+instance NFData OnDelete
152+
153+renderOnDelete :: OnDelete -> ByteString
154+renderOnDelete = \case
155+ OnDeleteNoAction -> "ON DELETE NO ACTION"
156+ OnDeleteRestrict -> "ON DELETE RESTRICT"
157+ OnDeleteCascade -> "ON DELETE CASCADE"
158+
159+data OnUpdate
160+ = OnUpdateNoAction
161+ | OnUpdateRestrict
162+ | OnUpdateCascade
163+ deriving (GHC.Generic,Show,Eq,Ord)
164+instance NFData OnUpdate
165+
166+renderOnUpdate :: OnUpdate -> ByteString
167+renderOnUpdate = \case
168+ OnUpdateNoAction -> "ON UPDATE NO ACTION"
169+ OnUpdateRestrict -> "ON UPDATE RESTRICT"
170+ OnUpdateCascade -> "ON UPDATE CASCADE"
171+
172+{-----------------------------------------
173+DROP statements
174+-----------------------------------------}
175+
176+dropTable
177+ :: KnownSymbol table
178+ => Alias table
179+ -> Definition schema (Drop table schema)
180+dropTable table = UnsafeDefinition $ "DROP TABLE" <+> renderAlias table <> ";"
181+
182+{-----------------------------------------
183+ALTER statements
184+-----------------------------------------}
185+
186+alterTable
187+ :: HasTable table schema columns0
188+ => Alias table
189+ -> AlterTable columns0 columns1
190+ -> Definition schema (Alter table schema columns1)
191+alterTable table alteration = UnsafeDefinition $
192+ "ALTER TABLE"
193+ <+> renderAlias table
194+ <+> renderAlterTable alteration
195+ <> ";"
196+
197+alterTableRename
198+ :: (KnownSymbol table0, KnownSymbol table1)
199+ => Alias table0
200+ -> Alias table1
201+ -> Definition schema (Rename table0 table1 schema)
202+alterTableRename table0 table1 = UnsafeDefinition $
203+ "ALTER TABLE" <+> renderAlias table0
204+ <+> "RENAME TO" <+> renderAlias table1 <> ";"
205+
206+newtype AlterTable
207+ (columns0 :: ColumnsType)
208+ (columns1 :: ColumnsType) =
209+ UnsafeAlterTable {renderAlterTable :: ByteString}
210+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
211+
212+addColumnDefault
213+ :: KnownSymbol column
214+ => Alias column
215+ -> TypeExpression ('Optional ty)
216+ -> AlterTable columns (Create column ('Optional ty) columns)
217+addColumnDefault column ty = UnsafeAlterTable $
218+ "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
219+
220+addColumnNull
221+ :: KnownSymbol column
222+ => Alias column
223+ -> TypeExpression ('Required ('Null ty))
224+ -> AlterTable columns (Create column ('Required ('Null ty)) columns)
225+addColumnNull column ty = UnsafeAlterTable $
226+ "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
227+
228+dropColumn
229+ :: KnownSymbol column
230+ => Alias column
231+ -> AlterTable columns (Drop column columns)
232+dropColumn column = UnsafeAlterTable $
233+ "DROP COLUMN" <+> renderAlias column
234+
235+renameColumn
236+ :: (KnownSymbol column0, KnownSymbol column1)
237+ => Alias column0
238+ -> Alias column1
239+ -> AlterTable columns (Rename column0 column1 columns)
240+renameColumn column0 column1 = UnsafeAlterTable $
241+ "RENAME COLUMN" <+> renderAlias column0 <+> "TO" <+> renderAlias column1
242+
243+alterColumn
244+ :: (KnownSymbol column, HasColumn column columns ty0)
245+ => Alias column
246+ -> AlterColumn ty0 ty1
247+ -> AlterTable columns (Alter column columns ty1)
248+alterColumn column alteration = UnsafeAlterTable $
249+ "ALTER COLUMN" <+> renderAlias column <+> renderAlterColumn alteration
250+
251+newtype AlterColumn (ty0 :: ColumnType) (ty1 :: ColumnType) =
252+ UnsafeAlterColumn {renderAlterColumn :: ByteString}
253+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
254+
255+setDefault
256+ :: Expression '[] 'Ungrouped '[] ('Required ty)
257+ -> AlterColumn ('Required ty) ('Optional ty)
258+setDefault expression = UnsafeAlterColumn $
259+ "SET DEFAULT" <+> renderExpression expression
260+
261+dropDefault :: AlterColumn ('Optional ty) ('Required ty)
262+dropDefault = UnsafeAlterColumn $ "DROP DEFAULT"
263+
264+setNotNull
265+ :: AlterColumn (optionality ('Null ty)) (optionality ('NotNull ty))
266+setNotNull = UnsafeAlterColumn $ "SET NOT NULL"
267+
268+dropNotNull
269+ :: AlterColumn (optionality ('NotNull ty)) (optionality ('Null ty))
270+dropNotNull = UnsafeAlterColumn $ "DROP NOT NULL"
271+
272+alterType
273+ :: TypeExpression (optionality (nullity ty1))
274+ -> AlterColumn (optionality (nullity ty0)) (optionality (nullity ty1))
275+alterType ty = UnsafeAlterColumn $ "TYPE " <> renderTypeExpression ty
squeal-postgresql/src/Squeal/PostgreSQL/Expression.hsnew file+905
@@ -0,0 +1,905 @@
1+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
2+{-# LANGUAGE
3+ AllowAmbiguousTypes
4+ , DataKinds
5+ , DeriveGeneric
6+ , DeriveDataTypeable
7+ , FlexibleContexts
8+ , FlexibleInstances
9+ , FunctionalDependencies
10+ , GADTs
11+ , GeneralizedNewtypeDeriving
12+ , LambdaCase
13+ , MagicHash
14+ , OverloadedStrings
15+ , PolyKinds
16+ , RankNTypes
17+ , ScopedTypeVariables
18+ , StandaloneDeriving
19+ , TypeApplications
20+ , TypeFamilies
21+ , TypeInType
22+ , TypeOperators
23+ , UndecidableInstances
24+#-}
25+
26+module Squeal.PostgreSQL.Expression
27+ ( -- * Column Expressions
28+ Expression (UnsafeExpression, renderExpression)
29+ , HasParameter (param)
30+ , HasColumn (getColumn)
31+ , Column (Column)
32+ , renderColumn
33+ , GroupedBy (getGroup1, getGroup2)
34+ -- ** Default
35+ , def
36+ , unDef
37+ -- ** Null
38+ , null_
39+ , unNull
40+ , coalesce
41+ , fromNull
42+ , isNull
43+ , isn'tNull
44+ , matchNull
45+ , nullIf
46+ -- ** Functions
47+ , unsafeBinaryOp
48+ , unsafeUnaryOp
49+ , unsafeFunction
50+ , atan2_
51+ , cast
52+ , div_
53+ , mod_
54+ , trunc
55+ , round_
56+ , ceiling_
57+ , greatest
58+ , least
59+ -- ** Conditions
60+ , Condition
61+ , true
62+ , false
63+ , not_
64+ , (.&&)
65+ , (.||)
66+ , caseWhenThenElse
67+ , ifThenElse
68+ , (.==)
69+ , (./=)
70+ , (.>=)
71+ , (.<)
72+ , (.<=)
73+ , (.>)
74+ -- ** Time
75+ , currentDate
76+ , currentTime
77+ , currentTimestamp
78+ , localTime
79+ , localTimestamp
80+ -- ** Text
81+ , lower
82+ , upper
83+ , charLength
84+ , like
85+ -- ** Aggregation
86+ , unsafeAggregate
87+ , unsafeAggregateDistinct
88+ , sum_
89+ , sumDistinct
90+ , PGAvg (avg, avgDistinct)
91+ , bitAnd
92+ , bitOr
93+ , boolAnd
94+ , boolOr
95+ , countStar
96+ , countDistinctStar
97+ , count
98+ , every
99+ , jsonAgg
100+ , jsonbAgg
101+ , jsonObjectAgg
102+ , jsonbObjectAgg
103+ , max_
104+ , min_
105+ -- * Tables
106+ , Table (UnsafeTable, renderTable)
107+ , HasTable (getTable)
108+ -- * TypeExpression
109+ , TypeExpression (UnsafeTypeExpression, renderTypeExpression)
110+ , PGTyped (pgtype)
111+ , bool
112+ , int2
113+ , smallint
114+ , int4
115+ , int
116+ , integer
117+ , int8
118+ , bigint
119+ , numeric
120+ , float4
121+ , real
122+ , float8
123+ , doublePrecision
124+ , serial2
125+ , smallserial
126+ , serial4
127+ , serial
128+ , serial8
129+ , bigserial
130+ , text
131+ , char
132+ , character
133+ , varchar
134+ , characterVarying
135+ , bytea
136+ , timestamp
137+ , timestampWithTimeZone
138+ , date
139+ , time
140+ , timeWithTimeZone
141+ , interval
142+ , uuid
143+ , json
144+ , jsonb
145+ , notNull
146+ , default_
147+ -- * Re-export
148+ , (&)
149+ , NP ((:*), Nil)
150+ ) where
151+
152+import Control.Category
153+import Control.DeepSeq
154+import Data.ByteString (ByteString)
155+import Data.Function ((&))
156+import Data.Monoid hiding (All)
157+import Data.Ratio
158+import Data.String
159+import Generics.SOP hiding (from)
160+import GHC.OverloadedLabels
161+import GHC.TypeLits
162+import Prelude hiding (id, (.))
163+
164+import qualified GHC.Generics as GHC
165+
166+import Squeal.PostgreSQL.Prettyprint
167+import Squeal.PostgreSQL.Schema
168+
169+{-----------------------------------------
170+column expressions
171+-----------------------------------------}
172+
173+{- | `Expression`s are used in a variety of contexts,
174+such as in the target list of the `select` command,
175+as new column values in `insertInto` or `update`,
176+or in search `Condition`s in a number of commands.
177+
178+The expression syntax allows the calculation of
179+values from primitive expression using arithmetic, logical,
180+and other operations.
181+-}
182+newtype Expression
183+ (tables :: TablesType)
184+ (grouping :: Grouping)
185+ (params :: [ColumnType])
186+ (ty :: ColumnType)
187+ = UnsafeExpression { renderExpression :: ByteString }
188+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
189+
190+{- | A `HasParameter` constraint is used to indicate a value that is
191+supplied externally to a SQL statement.
192+`Squeal.PostgreSQL.PQ.manipulateParams`,
193+`Squeal.PostgreSQL.PQ.queryParams` and
194+`Squeal.PostgreSQL.PQ.traversePrepared` support specifying data values
195+separately from the SQL command string, in which case `param`s are used to
196+refer to the out-of-line data values.
197+-}
198+class (PGTyped (BaseType ty), KnownNat n)
199+ => HasParameter (n :: Nat) params ty | n params -> ty where
200+ param :: Expression tables grouping params ty
201+ param = UnsafeExpression $ parenthesized $
202+ "$" <> renderNat (Proxy @n) <+> "::"
203+ <+> renderTypeExpression (pgtype @(BaseType ty))
204+instance {-# OVERLAPPING #-} PGTyped (BaseType ty1)
205+ => HasParameter 1 (ty1:tys) ty1
206+instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) params ty)
207+ => HasParameter n (ty' : params) ty
208+
209+{- | A `HasColumn` constraint indicates an unqualified column reference.
210+`getColumn` can only be unambiguous when the `TableExpression` the column
211+references is unique, in which case the column may be referenced using
212+@-XOverloadedLabels@. Otherwise, combined with a `HasTable` constraint, the
213+qualified column reference operator `!` may be used.
214+-}
215+class KnownSymbol column => HasColumn column columns ty
216+ | column columns -> ty where
217+ getColumn
218+ :: HasUnique table tables columns
219+ => Alias column
220+ -> Expression tables 'Ungrouped params ty
221+ getColumn column = UnsafeExpression $ renderAlias column
222+instance {-# OVERLAPPING #-} KnownSymbol column
223+ => HasColumn column ((column ::: optionality ty) ': tys) ('Required ty)
224+instance {-# OVERLAPPABLE #-} (KnownSymbol column, HasColumn column table ty)
225+ => HasColumn column (ty' ': table) ty
226+
227+instance (HasColumn column columns ty, HasUnique table tables columns)
228+ => IsLabel column (Expression tables 'Ungrouped params ty) where
229+ fromLabel = getColumn (Alias @column)
230+
231+instance (HasTable table tables columns, HasColumn column columns ty)
232+ => IsTableColumn table column (Expression tables 'Ungrouped params ty) where
233+ table ! column = UnsafeExpression $
234+ renderAlias table <> "." <> renderAlias column
235+
236+data Column
237+ (columns :: ColumnsType)
238+ (columnty :: (Symbol,ColumnType))
239+ where
240+ Column
241+ :: HasColumn column columns ty
242+ => Alias column
243+ -> Column columns (column ::: ty)
244+deriving instance Show (Column columns columnty)
245+deriving instance Eq (Column columns columnty)
246+deriving instance Ord (Column columns columnty)
247+
248+renderColumn :: Column columns columnty -> ByteString
249+renderColumn (Column column) = renderAlias column
250+
251+class (KnownSymbol table, KnownSymbol column)
252+ => GroupedBy table column bys where
253+ getGroup1
254+ :: (HasUnique table tables columns, HasColumn column columns ty)
255+ => Alias column
256+ -> Expression tables ('Grouped bys) params ty
257+ getGroup1 column = UnsafeExpression $ renderAlias column
258+ getGroup2
259+ :: (HasTable table tables columns, HasColumn column columns ty)
260+ => Alias table
261+ -> Alias column
262+ -> Expression tables ('Grouped bys) params ty
263+ getGroup2 table column = UnsafeExpression $
264+ renderAlias table <> "." <> renderAlias column
265+instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
266+ => GroupedBy table column ('(table,column) ': bys)
267+instance {-# OVERLAPPABLE #-}
268+ ( KnownSymbol table
269+ , KnownSymbol column
270+ , GroupedBy table column bys
271+ ) => GroupedBy table column (tabcol ': bys)
272+
273+instance
274+ ( HasUnique table tables columns
275+ , HasColumn column columns ty
276+ , GroupedBy table column bys
277+ ) => IsLabel column
278+ (Expression tables ('Grouped bys) params ty) where
279+ fromLabel = getGroup1 (Alias @column)
280+
281+instance
282+ ( HasTable table tables columns
283+ , HasColumn column columns ty
284+ , GroupedBy table column bys
285+ ) => IsTableColumn table column
286+ (Expression tables ('Grouped bys) params ty) where (!) = getGroup2
287+
288+-- | >>> renderExpression def
289+-- "DEFAULT"
290+def :: Expression '[] 'Ungrouped params ('Optional (nullity ty))
291+def = UnsafeExpression "DEFAULT"
292+
293+-- | >>> renderExpression $ unDef false
294+-- "FALSE"
295+unDef
296+ :: Expression '[] 'Ungrouped params ('Required (nullity ty))
297+ -> Expression '[] 'Ungrouped params ('Optional (nullity ty))
298+unDef = UnsafeExpression . renderExpression
299+
300+-- | analagous to `Nothing`
301+--
302+-- >>> renderExpression $ null_
303+-- "NULL"
304+null_ :: Expression tables grouping params (optionality ('Null ty))
305+null_ = UnsafeExpression "NULL"
306+
307+-- | analagous to `Just`
308+--
309+-- >>> renderExpression $ unNull true
310+-- "TRUE"
311+unNull
312+ :: Expression tables grouping params (optionality ('NotNull ty))
313+ -> Expression tables grouping params (optionality ('Null ty))
314+unNull = UnsafeExpression . renderExpression
315+
316+-- | return the leftmost value which is not NULL
317+--
318+-- >>> renderExpression $ coalesce [null_, unNull true] false
319+-- "COALESCE(NULL, TRUE, FALSE)"
320+coalesce
321+ :: [Expression tables grouping params ('Required ('Null ty))]
322+ -> Expression tables grouping params ('Required ('NotNull ty))
323+ -> Expression tables grouping params ('Required ('NotNull ty))
324+coalesce nullxs notNullx = UnsafeExpression $
325+ "COALESCE" <> parenthesized (commaSeparated
326+ ((renderExpression <$> nullxs) <> [renderExpression notNullx]))
327+
328+-- | analagous to `fromMaybe` using @COALESCE@
329+--
330+-- >>> renderExpression $ fromNull true null_
331+-- "COALESCE(NULL, TRUE)"
332+fromNull
333+ :: Expression tables grouping params ('Required ('NotNull ty))
334+ -> Expression tables grouping params ('Required ('Null ty))
335+ -> Expression tables grouping params ('Required ('NotNull ty))
336+fromNull notNullx nullx = coalesce [nullx] notNullx
337+
338+-- | >>> renderExpression $ null_ & isNull
339+-- "NULL IS NULL"
340+isNull
341+ :: Expression tables grouping params ('Required ('Null ty))
342+ -> Condition tables grouping params
343+isNull x = UnsafeExpression $ renderExpression x <+> "IS NULL"
344+
345+-- | >>> renderExpression $ null_ & isn'tNull
346+-- "NULL IS NOT NULL"
347+isn'tNull
348+ :: Expression tables grouping params ('Required ('Null ty))
349+ -> Condition tables grouping params
350+isn'tNull x = UnsafeExpression $ renderExpression x <+> "IS NOT NULL"
351+
352+-- | analagous to `maybe` using @IS NULL@
353+--
354+-- >>> renderExpression $ matchNull true not_ null_
355+-- "CASE WHEN NULL IS NULL THEN TRUE ELSE (NOT NULL) END"
356+matchNull
357+ :: Expression tables grouping params ('Required nullty)
358+ -> ( Expression tables grouping params ('Required ('NotNull ty))
359+ -> Expression tables grouping params ('Required nullty) )
360+ -> Expression tables grouping params ('Required ('Null ty))
361+ -> Expression tables grouping params ('Required nullty)
362+matchNull y f x = ifThenElse (isNull x) y
363+ (f (UnsafeExpression (renderExpression x)))
364+
365+-- | right inverse to `fromNull`
366+--
367+-- >>> :set -XTypeApplications -XDataKinds
368+-- >>> renderExpression @_ @_ @'[_] $ fromNull false (nullIf false (param @1))
369+-- "COALESCE(NULL IF (FALSE, ($1 :: bool)), FALSE)"
370+nullIf
371+ :: Expression tables grouping params ('Required ('NotNull ty))
372+ -> Expression tables grouping params ('Required ('NotNull ty))
373+ -> Expression tables grouping params ('Required ('Null ty))
374+nullIf x y = UnsafeExpression $ "NULL IF" <+> parenthesized
375+ (renderExpression x <> ", " <> renderExpression y)
376+
377+-- | >>> renderExpression @_ @_ @'[_] $ greatest currentTimestamp [param @1]
378+-- "GREATEST(CURRENT_TIMESTAMP, ($1 :: timestamp with time zone))"
379+greatest
380+ :: Expression tables grouping params ('Required nullty)
381+ -> [Expression tables grouping params ('Required nullty)]
382+ -> Expression tables grouping params ('Required nullty)
383+greatest x xs = UnsafeExpression $ "GREATEST("
384+ <> commaSeparated (renderExpression <$> (x:xs)) <> ")"
385+
386+-- | >>> renderExpression $ least currentTimestamp [null_]
387+-- "LEAST(CURRENT_TIMESTAMP, NULL)"
388+least
389+ :: Expression tables grouping params ('Required nullty)
390+ -> [Expression tables grouping params ('Required nullty)]
391+ -> Expression tables grouping params ('Required nullty)
392+least x xs = UnsafeExpression $ "LEAST("
393+ <> commaSeparated (renderExpression <$> (x:xs)) <> ")"
394+
395+unsafeBinaryOp
396+ :: ByteString
397+ -> Expression tables grouping params ('Required ty0)
398+ -> Expression tables grouping params ('Required ty1)
399+ -> Expression tables grouping params ('Required ty2)
400+unsafeBinaryOp op x y = UnsafeExpression $ parenthesized $
401+ renderExpression x <+> op <+> renderExpression y
402+
403+unsafeUnaryOp
404+ :: ByteString
405+ -> Expression tables grouping params ('Required ty0)
406+ -> Expression tables grouping params ('Required ty1)
407+unsafeUnaryOp op x = UnsafeExpression $ parenthesized $
408+ op <+> renderExpression x
409+
410+unsafeFunction
411+ :: ByteString
412+ -> Expression tables grouping params ('Required xty)
413+ -> Expression tables grouping params ('Required yty)
414+unsafeFunction fun x = UnsafeExpression $
415+ fun <> parenthesized (renderExpression x)
416+
417+instance PGNum ty
418+ => Num (Expression tables grouping params ('Required (nullity ty))) where
419+ (+) = unsafeBinaryOp "+"
420+ (-) = unsafeBinaryOp "-"
421+ (*) = unsafeBinaryOp "*"
422+ abs = unsafeFunction "abs"
423+ signum = unsafeFunction "sign"
424+ fromInteger
425+ = UnsafeExpression
426+ . fromString
427+ . show
428+
429+instance (PGNum ty, PGFloating ty) => Fractional
430+ (Expression tables grouping params ('Required (nullity ty))) where
431+ (/) = unsafeBinaryOp "/"
432+ fromRational x = fromInteger (numerator x) / fromInteger (denominator x)
433+
434+instance (PGNum ty, PGFloating ty) => Floating
435+ (Expression tables grouping params ('Required (nullity ty))) where
436+ pi = UnsafeExpression "pi()"
437+ exp = unsafeFunction "exp"
438+ log = unsafeFunction "ln"
439+ sqrt = unsafeFunction "sqrt"
440+ b ** x = UnsafeExpression $
441+ "power(" <> renderExpression b <> ", " <> renderExpression x <> ")"
442+ logBase b y = log y / log b
443+ sin = unsafeFunction "sin"
444+ cos = unsafeFunction "cos"
445+ tan = unsafeFunction "tan"
446+ asin = unsafeFunction "asin"
447+ acos = unsafeFunction "acos"
448+ atan = unsafeFunction "atan"
449+ sinh x = (exp x - exp (-x)) / 2
450+ cosh x = (exp x + exp (-x)) / 2
451+ tanh x = sinh x / cosh x
452+ asinh x = log (x + sqrt (x*x + 1))
453+ acosh x = log (x + sqrt (x*x - 1))
454+ atanh x = log ((1 + x) / (1 - x)) / 2
455+
456+atan2_
457+ :: PGFloating float
458+ => Expression tables grouping params ('Required (nullity float))
459+ -> Expression tables grouping params ('Required (nullity float))
460+ -> Expression tables grouping params ('Required (nullity float))
461+atan2_ y x = UnsafeExpression $
462+ "atan2(" <> renderExpression y <> ", " <> renderExpression x <> ")"
463+
464+cast
465+ :: TypeExpression ('Required ('Null ty1))
466+ -> Expression tables grouping params ('Required (nullity ty0))
467+ -> Expression tables grouping params ('Required (nullity ty1))
468+cast ty x = UnsafeExpression $ parenthesized $
469+ renderExpression x <+> "::" <+> renderTypeExpression ty
470+
471+div_
472+ :: PGIntegral int
473+ => Expression tables grouping params ('Required (nullity int))
474+ -> Expression tables grouping params ('Required (nullity int))
475+ -> Expression tables grouping params ('Required (nullity int))
476+div_ = unsafeBinaryOp "/"
477+
478+mod_
479+ :: PGIntegral int
480+ => Expression tables grouping params ('Required (nullity int))
481+ -> Expression tables grouping params ('Required (nullity int))
482+ -> Expression tables grouping params ('Required (nullity int))
483+mod_ = unsafeBinaryOp "%"
484+
485+trunc
486+ :: PGFloating frac
487+ => Expression tables grouping params ('Required (nullity frac))
488+ -> Expression tables grouping params ('Required (nullity frac))
489+trunc = unsafeFunction "trunc"
490+
491+round_
492+ :: PGFloating frac
493+ => Expression tables grouping params ('Required (nullity frac))
494+ -> Expression tables grouping params ('Required (nullity frac))
495+round_ = unsafeFunction "round"
496+
497+ceiling_
498+ :: PGFloating frac
499+ => Expression tables grouping params ('Required (nullity frac))
500+ -> Expression tables grouping params ('Required (nullity frac))
501+ceiling_ = unsafeFunction "ceiling"
502+
503+type Condition tables grouping params =
504+ Expression tables grouping params ('Required ('NotNull 'PGbool))
505+
506+true :: Condition tables grouping params
507+true = UnsafeExpression "TRUE"
508+
509+false :: Condition tables grouping params
510+false = UnsafeExpression "FALSE"
511+
512+not_
513+ :: Condition tables grouping params
514+ -> Condition tables grouping params
515+not_ = unsafeUnaryOp "NOT"
516+
517+(.&&)
518+ :: Condition tables grouping params
519+ -> Condition tables grouping params
520+ -> Condition tables grouping params
521+(.&&) = unsafeBinaryOp "AND"
522+
523+(.||)
524+ :: Condition tables grouping params
525+ -> Condition tables grouping params
526+ -> Condition tables grouping params
527+(.||) = unsafeBinaryOp "OR"
528+
529+caseWhenThenElse
530+ :: [ ( Condition tables grouping params
531+ , Expression tables grouping params ('Required ty)
532+ ) ]
533+ -> Expression tables grouping params ('Required ty)
534+ -> Expression tables grouping params ('Required ty)
535+caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
536+ [ "CASE"
537+ , mconcat
538+ [ mconcat
539+ [ " WHEN ", renderExpression when_
540+ , " THEN ", renderExpression then_
541+ ]
542+ | (when_,then_) <- whenThens
543+ ]
544+ , " ELSE ", renderExpression else_
545+ , " END"
546+ ]
547+
548+ifThenElse
549+ :: Condition tables grouping params
550+ -> Expression tables grouping params ('Required ty)
551+ -> Expression tables grouping params ('Required ty)
552+ -> Expression tables grouping params ('Required ty)
553+ifThenElse if_ then_ else_ = caseWhenThenElse [(if_,then_)] else_
554+
555+(.==)
556+ :: Expression tables grouping params ('Required (nullity ty))
557+ -> Expression tables grouping params ('Required (nullity ty))
558+ -> Expression tables grouping params ('Required (nullity 'PGbool))
559+(.==) = unsafeBinaryOp "="
560+infix 4 .==
561+
562+(./=)
563+ :: Expression tables grouping params ('Required (nullity ty))
564+ -> Expression tables grouping params ('Required (nullity ty))
565+ -> Expression tables grouping params ('Required (nullity 'PGbool))
566+(./=) = unsafeBinaryOp "<>"
567+infix 4 ./=
568+
569+(.>=)
570+ :: Expression tables grouping params ('Required (nullity ty))
571+ -> Expression tables grouping params ('Required (nullity ty))
572+ -> Expression tables grouping params ('Required (nullity 'PGbool))
573+(.>=) = unsafeBinaryOp ">="
574+infix 4 .>=
575+
576+(.<)
577+ :: Expression tables grouping params ('Required (nullity ty))
578+ -> Expression tables grouping params ('Required (nullity ty))
579+ -> Expression tables grouping params ('Required (nullity 'PGbool))
580+(.<) = unsafeBinaryOp "<"
581+infix 4 .<
582+
583+(.<=)
584+ :: Expression tables grouping params ('Required (nullity ty))
585+ -> Expression tables grouping params ('Required (nullity ty))
586+ -> Expression tables grouping params ('Required (nullity 'PGbool))
587+(.<=) = unsafeBinaryOp "<="
588+infix 4 .<=
589+
590+(.>)
591+ :: Expression tables grouping params ('Required (nullity ty))
592+ -> Expression tables grouping params ('Required (nullity ty))
593+ -> Expression tables grouping params ('Required (nullity 'PGbool))
594+(.>) = unsafeBinaryOp ">"
595+infix 4 .>
596+
597+currentDate
598+ :: Expression tables grouping params ('Required (nullity 'PGdate))
599+currentDate = UnsafeExpression "CURRENT_DATE"
600+
601+currentTime
602+ :: Expression tables grouping params ('Required (nullity 'PGtimetz))
603+currentTime = UnsafeExpression "CURRENT_TIME"
604+
605+currentTimestamp
606+ :: Expression tables grouping params ('Required (nullity 'PGtimestamptz))
607+currentTimestamp = UnsafeExpression "CURRENT_TIMESTAMP"
608+
609+localTime
610+ :: Expression tables grouping params ('Required (nullity 'PGtime))
611+localTime = UnsafeExpression "LOCALTIME"
612+
613+localTimestamp
614+ :: Expression tables grouping params ('Required (nullity 'PGtimestamp))
615+localTimestamp = UnsafeExpression "LOCALTIMESTAMP"
616+
617+{-----------------------------------------
618+text
619+-----------------------------------------}
620+
621+instance IsString
622+ (Expression tables grouping params ('Required (nullity 'PGtext))) where
623+ fromString str = UnsafeExpression $
624+ "E\'" <> fromString (escape =<< str) <> "\'"
625+ where
626+ escape = \case
627+ '\NUL' -> "\\0"
628+ '\'' -> "''"
629+ '"' -> "\\\""
630+ '\b' -> "\\b"
631+ '\n' -> "\\n"
632+ '\r' -> "\\r"
633+ '\t' -> "\\t"
634+ '\\' -> "\\\\"
635+ c -> [c]
636+
637+instance Monoid
638+ (Expression tables grouping params ('Required (nullity 'PGtext))) where
639+ mempty = fromString ""
640+ mappend = unsafeBinaryOp "||"
641+
642+lower
643+ :: Expression tables grouping params ('Required (nullity 'PGtext))
644+ -> Expression tables grouping params ('Required (nullity 'PGtext))
645+lower = unsafeFunction "lower"
646+
647+upper
648+ :: Expression tables grouping params ('Required (nullity 'PGtext))
649+ -> Expression tables grouping params ('Required (nullity 'PGtext))
650+upper = unsafeFunction "upper"
651+
652+charLength
653+ :: Expression tables grouping params ('Required (nullity 'PGtext))
654+ -> Expression tables grouping params ('Required (nullity 'PGint4))
655+charLength = unsafeFunction "char_length"
656+
657+like
658+ :: Expression tables grouping params ('Required (nullity 'PGtext))
659+ -> Expression tables grouping params ('Required (nullity 'PGtext))
660+ -> Expression tables grouping params ('Required (nullity 'PGbool))
661+like = unsafeBinaryOp "LIKE"
662+
663+{-----------------------------------------
664+aggregation
665+-----------------------------------------}
666+
667+unsafeAggregate, unsafeAggregateDistinct
668+ :: ByteString
669+ -> Expression tables 'Ungrouped params ('Required xty)
670+ -> Expression tables ('Grouped bys) params ('Required yty)
671+unsafeAggregate fun x = UnsafeExpression $ mconcat
672+ [fun, "(", renderExpression x, ")"]
673+unsafeAggregateDistinct fun x = UnsafeExpression $ mconcat
674+ [fun, "(DISTINCT ", renderExpression x, ")"]
675+
676+sum_, sumDistinct
677+ :: PGNum ty
678+ => Expression tables 'Ungrouped params ('Required (nullity ty))
679+ -> Expression tables ('Grouped bys) params ('Required (nullity ty))
680+sum_ = unsafeAggregate "sum"
681+sumDistinct = unsafeAggregateDistinct "sum"
682+
683+class PGAvg ty avg | ty -> avg where
684+ avg, avgDistinct
685+ :: Expression tables 'Ungrouped params ('Required (nullity ty))
686+ -> Expression tables ('Grouped bys) params ('Required (nullity avg))
687+ avg = unsafeAggregate "avg"
688+ avgDistinct = unsafeAggregateDistinct "avg"
689+instance PGAvg 'PGint2 'PGnumeric
690+instance PGAvg 'PGint4 'PGnumeric
691+instance PGAvg 'PGint8 'PGnumeric
692+instance PGAvg 'PGnumeric 'PGnumeric
693+instance PGAvg 'PGfloat4 'PGfloat8
694+instance PGAvg 'PGfloat8 'PGfloat8
695+instance PGAvg 'PGinterval 'PGinterval
696+
697+bitAnd, bitOr
698+ :: PGIntegral int
699+ => Expression tables 'Ungrouped params ('Required (nullity int))
700+ -> Expression tables ('Grouped bys) params ('Required (nullity int))
701+bitAnd = unsafeAggregate "bit_and"
702+bitOr = unsafeAggregate "bit_or"
703+
704+boolAnd, boolOr
705+ :: Expression tables 'Ungrouped params ('Required (nullity 'PGbool))
706+ -> Expression tables ('Grouped bys) params ('Required (nullity 'PGbool))
707+boolAnd = unsafeAggregate "bool_and"
708+boolOr = unsafeAggregate "bool_or"
709+
710+countStar, countDistinctStar
711+ :: Expression tables ('Grouped bys) params ('Required ('NotNull 'PGint8))
712+countStar = UnsafeExpression $ "count(*)"
713+countDistinctStar = UnsafeExpression $ "count(DISTINCT *)"
714+
715+count
716+ :: Expression tables 'Ungrouped params ('Required ty)
717+ -> Expression tables ('Grouped bys) params ('Required ('NotNull 'PGint8))
718+count = unsafeAggregate "count"
719+
720+every
721+ :: Expression tables 'Ungrouped params ('Required (nullity 'PGbool))
722+ -> Expression tables ('Grouped bys) params ('Required (nullity 'PGbool))
723+every = unsafeAggregate "every"
724+
725+jsonAgg
726+ :: Expression tables 'Ungrouped params ('Required ty)
727+ -> Expression tables ('Grouped bys) params ('Required ('NotNull 'PGjson))
728+jsonAgg = unsafeAggregate "json_agg"
729+
730+jsonbAgg
731+ :: Expression tables 'Ungrouped params ('Required ty)
732+ -> Expression tables ('Grouped bys) params ('Required ('NotNull 'PGjsonb))
733+jsonbAgg = unsafeAggregate "jsonb_agg"
734+
735+jsonObjectAgg
736+ :: Expression tables 'Ungrouped params ('Required ('NotNull keyty))
737+ -> Expression tables 'Ungrouped params ('Required valty)
738+ -> Expression tables ('Grouped bys) params ('Required ('NotNull 'PGjson))
739+jsonObjectAgg k v = UnsafeExpression $
740+ "json_object_agg(" <> renderExpression k
741+ <> ", " <> renderExpression v <> ")"
742+
743+jsonbObjectAgg
744+ :: Expression tables 'Ungrouped params ('Required ('NotNull keyty))
745+ -> Expression tables 'Ungrouped params ('Required valty)
746+ -> Expression tables ('Grouped bys) params ('Required ('NotNull 'PGjsonb))
747+jsonbObjectAgg k v = UnsafeExpression $
748+ "jsonb_object_agg("
749+ <> renderExpression k <> ", " <> renderExpression v <> ")"
750+
751+max_, min_
752+ :: Expression tables 'Ungrouped params ('Required (nullity ty))
753+ -> Expression tables ('Grouped bys) params ('Required (nullity ty))
754+max_ = unsafeAggregate "max"
755+min_ = unsafeAggregate "min"
756+
757+{-----------------------------------------
758+tables
759+-----------------------------------------}
760+
761+newtype Table
762+ (schema :: TablesType)
763+ (columns :: ColumnsType)
764+ = UnsafeTable { renderTable :: ByteString }
765+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
766+
767+class KnownSymbol table => HasTable table tables columns
768+ | table tables -> columns where
769+ getTable :: Alias table -> Table tables columns
770+ getTable table = UnsafeTable $ renderAlias table
771+instance {-# OVERLAPPING #-} KnownSymbol table
772+ => HasTable table ((table ::: columns) ': tables) columns
773+instance {-# OVERLAPPABLE #-}
774+ (KnownSymbol table, HasTable table schema columns)
775+ => HasTable table (table' ': schema) columns
776+
777+instance HasTable table schema columns
778+ => IsLabel table (Table schema columns) where
779+ fromLabel = getTable (Alias @table)
780+
781+{-----------------------------------------
782+type expressions
783+-----------------------------------------}
784+
785+newtype TypeExpression (ty :: ColumnType)
786+ = UnsafeTypeExpression { renderTypeExpression :: ByteString }
787+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
788+
789+bool :: TypeExpression ('Required ('Null 'PGbool))
790+bool = UnsafeTypeExpression "bool"
791+int2 :: TypeExpression ('Required ('Null 'PGint2))
792+int2 = UnsafeTypeExpression "int2"
793+smallint :: TypeExpression ('Required ('Null 'PGint2))
794+smallint = UnsafeTypeExpression "smallint"
795+int4 :: TypeExpression ('Required ('Null 'PGint4))
796+int4 = UnsafeTypeExpression "int4"
797+int :: TypeExpression ('Required ('Null 'PGint4))
798+int = UnsafeTypeExpression "int"
799+integer :: TypeExpression ('Required ('Null 'PGint4))
800+integer = UnsafeTypeExpression "integer"
801+int8 :: TypeExpression ('Required ('Null 'PGint8))
802+int8 = UnsafeTypeExpression "int8"
803+bigint :: TypeExpression ('Required ('Null 'PGint8))
804+bigint = UnsafeTypeExpression "bigint"
805+numeric :: TypeExpression ('Required ('Null 'PGnumeric))
806+numeric = UnsafeTypeExpression "numeric"
807+float4 :: TypeExpression ('Required ('Null 'PGfloat4))
808+float4 = UnsafeTypeExpression "float4"
809+real :: TypeExpression ('Required ('Null 'PGfloat4))
810+real = UnsafeTypeExpression "real"
811+float8 :: TypeExpression ('Required ('Null 'PGfloat8))
812+float8 = UnsafeTypeExpression "float8"
813+doublePrecision :: TypeExpression ('Required ('Null 'PGfloat8))
814+doublePrecision = UnsafeTypeExpression "double precision"
815+serial2 :: TypeExpression ('Optional ('NotNull 'PGint2))
816+serial2 = UnsafeTypeExpression "serial2"
817+smallserial :: TypeExpression ('Optional ('NotNull 'PGint2))
818+smallserial = UnsafeTypeExpression "smallserial"
819+serial4 :: TypeExpression ('Optional ('NotNull 'PGint4))
820+serial4 = UnsafeTypeExpression "serial4"
821+serial :: TypeExpression ('Optional ('NotNull 'PGint4))
822+serial = UnsafeTypeExpression "serial"
823+serial8 :: TypeExpression ('Optional ('NotNull 'PGint8))
824+serial8 = UnsafeTypeExpression "serial8"
825+bigserial :: TypeExpression ('Optional ('NotNull 'PGint8))
826+bigserial = UnsafeTypeExpression "bigserial"
827+text :: TypeExpression ('Required ('Null 'PGtext))
828+text = UnsafeTypeExpression "text"
829+char
830+ :: (KnownNat n, 1 <= n)
831+ => proxy n
832+ -> TypeExpression ('Required ('Null ('PGchar n)))
833+char p = UnsafeTypeExpression $ "char(" <> renderNat p <> ")"
834+character
835+ :: (KnownNat n, 1 <= n)
836+ => proxy n
837+ -> TypeExpression ('Required ('Null ('PGchar n)))
838+character p = UnsafeTypeExpression $ "character(" <> renderNat p <> ")"
839+varchar
840+ :: (KnownNat n, 1 <= n)
841+ => proxy n
842+ -> TypeExpression ('Required ('Null ('PGvarchar n)))
843+varchar p = UnsafeTypeExpression $ "varchar(" <> renderNat p <> ")"
844+characterVarying
845+ :: (KnownNat n, 1 <= n)
846+ => proxy n
847+ -> TypeExpression ('Required ('Null ('PGvarchar n)))
848+characterVarying p = UnsafeTypeExpression $
849+ "character varying(" <> renderNat p <> ")"
850+bytea :: TypeExpression ('Required ('Null ('PGbytea)))
851+bytea = UnsafeTypeExpression "bytea"
852+timestamp :: TypeExpression ('Required ('Null ('PGtimestamp)))
853+timestamp = UnsafeTypeExpression "timestamp"
854+timestampWithTimeZone :: TypeExpression ('Required ('Null ('PGtimestamptz)))
855+timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
856+date :: TypeExpression ('Required ('Null ('PGdate)))
857+date = UnsafeTypeExpression "date"
858+time :: TypeExpression ('Required ('Null ('PGtime)))
859+time = UnsafeTypeExpression "time"
860+timeWithTimeZone :: TypeExpression ('Required ('Null ('PGtimetz)))
861+timeWithTimeZone = UnsafeTypeExpression "time with time zone"
862+interval :: TypeExpression ('Required ('Null ('PGinterval)))
863+interval = UnsafeTypeExpression "interval"
864+uuid :: TypeExpression ('Required ('Null ('PGuuid)))
865+uuid = UnsafeTypeExpression "uuid"
866+json :: TypeExpression ('Required ('Null ('PGjson)))
867+json = UnsafeTypeExpression "json"
868+jsonb :: TypeExpression ('Required ('Null ('PGjsonb)))
869+jsonb = UnsafeTypeExpression "jsonb"
870+
871+notNull
872+ :: TypeExpression (optionality ('Null ty))
873+ -> TypeExpression (optionality ('NotNull ty))
874+notNull ty = UnsafeTypeExpression $ renderTypeExpression ty <+> "NOT NULL"
875+default_
876+ :: Expression '[] 'Ungrouped '[] ('Required ty)
877+ -> TypeExpression ('Required ty)
878+ -> TypeExpression ('Optional ty)
879+default_ x ty = UnsafeTypeExpression $
880+ renderTypeExpression ty <+> "DEFAULT" <+> renderExpression x
881+
882+class PGTyped (ty :: PGType) where
883+ pgtype :: TypeExpression ('Required ('Null ty))
884+instance PGTyped 'PGbool where pgtype = bool
885+instance PGTyped 'PGint2 where pgtype = int2
886+instance PGTyped 'PGint4 where pgtype = int4
887+instance PGTyped 'PGint8 where pgtype = int8
888+instance PGTyped 'PGnumeric where pgtype = numeric
889+instance PGTyped 'PGfloat4 where pgtype = float4
890+instance PGTyped 'PGfloat8 where pgtype = float8
891+instance PGTyped 'PGtext where pgtype = text
892+instance (KnownNat n, 1 <= n)
893+ => PGTyped ('PGchar n) where pgtype = char (Proxy @n)
894+instance (KnownNat n, 1 <= n)
895+ => PGTyped ('PGvarchar n) where pgtype = varchar (Proxy @n)
896+instance PGTyped 'PGbytea where pgtype = bytea
897+instance PGTyped 'PGtimestamp where pgtype = timestamp
898+instance PGTyped 'PGtimestamptz where pgtype = timestampWithTimeZone
899+instance PGTyped 'PGdate where pgtype = date
900+instance PGTyped 'PGtime where pgtype = time
901+instance PGTyped 'PGtimetz where pgtype = timeWithTimeZone
902+instance PGTyped 'PGinterval where pgtype = interval
903+instance PGTyped 'PGuuid where pgtype = uuid
904+instance PGTyped 'PGjson where pgtype = json
905+instance PGTyped 'PGjsonb where pgtype = jsonb
squeal-postgresql/src/Squeal/PostgreSQL/Manipulation.hsnew file+189
@@ -0,0 +1,189 @@
1+{-# LANGUAGE
2+ DataKinds
3+ , DeriveDataTypeable
4+ , DeriveGeneric
5+ , GADTs
6+ , GeneralizedNewtypeDeriving
7+ , KindSignatures
8+ , LambdaCase
9+ , OverloadedStrings
10+ , RankNTypes
11+ , StandaloneDeriving
12+ , TypeInType
13+ , TypeOperators
14+#-}
15+
16+module Squeal.PostgreSQL.Manipulation
17+ ( -- * Data Manipulation Language
18+ Manipulation (UnsafeManipulation, renderManipulation)
19+ , queryStatement
20+ , insertInto
21+ , ValuesClause (Values, ValuesQuery)
22+ , renderValuesClause
23+ , ReturningClause (ReturningStar, Returning)
24+ , renderReturningClause
25+ , ConflictClause (Conflict, OnConflictDoNothing, OnConflictDoUpdate)
26+ , renderConflictClause
27+ , UpdateExpression (Same, Set)
28+ , renderUpdateExpression
29+ , update
30+ , deleteFrom
31+ ) where
32+
33+import Control.DeepSeq
34+import Data.ByteString
35+import Data.Monoid
36+import Generics.SOP
37+
38+import qualified GHC.Generics as GHC
39+
40+import Squeal.PostgreSQL.Expression
41+import Squeal.PostgreSQL.Prettyprint
42+import Squeal.PostgreSQL.Query
43+import Squeal.PostgreSQL.Schema
44+
45+newtype Manipulation
46+ (schema :: TablesType)
47+ (params :: [ColumnType])
48+ (columns :: ColumnsType)
49+ = UnsafeManipulation { renderManipulation :: ByteString }
50+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
51+
52+queryStatement
53+ :: Query schema params columns
54+ -> Manipulation schema params columns
55+queryStatement q = UnsafeManipulation $ renderQuery q <> ";"
56+
57+{-----------------------------------------
58+INSERT statements
59+-----------------------------------------}
60+
61+insertInto
62+ :: (SListI columns, SListI results, HasTable table schema columns)
63+ => Alias table
64+ -> ValuesClause schema params columns
65+ -> ConflictClause columns params
66+ -> ReturningClause columns params results
67+ -> Manipulation schema params results
68+insertInto table insert conflict returning = UnsafeManipulation $
69+ "INSERT" <+> "INTO" <+> renderAlias table
70+ <+> renderValuesClause insert
71+ <> renderConflictClause conflict
72+ <> renderReturningClause returning
73+
74+data ValuesClause
75+ (schema :: TablesType)
76+ (params :: [ColumnType])
77+ (columns :: ColumnsType)
78+ = Values
79+ (NP (Aliased (Expression '[] 'Ungrouped params)) columns)
80+ [NP (Aliased (Expression '[] 'Ungrouped params)) columns]
81+ | ValuesQuery (Query schema params columns)
82+
83+renderValuesClause
84+ :: SListI columns
85+ => ValuesClause schema params columns
86+ -> ByteString
87+renderValuesClause = \case
88+ Values row rows ->
89+ parenthesized (renderCommaSeparated renderAliasPart row)
90+ <+> "VALUES"
91+ <+> commaSeparated
92+ (parenthesized . renderCommaSeparated renderValuePart <$> row:rows)
93+ where
94+ renderAliasPart, renderValuePart
95+ :: Aliased (Expression '[] 'Ungrouped params) ty -> ByteString
96+ renderAliasPart (_ `As` name) = renderAlias name
97+ renderValuePart (value `As` _) = renderExpression value
98+ ValuesQuery q -> renderQuery q
99+
100+data ReturningClause
101+ (columns :: ColumnsType)
102+ (params :: [ColumnType])
103+ (results :: ColumnsType)
104+ where
105+ ReturningStar :: ReturningClause columns params columns
106+ Returning
107+ :: NP
108+ (Aliased (Expression '[table ::: columns] 'Ungrouped params))
109+ results
110+ -> ReturningClause columns params results
111+
112+renderReturningClause
113+ :: SListI results
114+ => ReturningClause params columns results
115+ -> ByteString
116+renderReturningClause = \case
117+ ReturningStar -> " RETURNING *;"
118+ Returning Nil -> ";"
119+ Returning results -> " RETURNING"
120+ <+> renderCommaSeparated (renderAliased renderExpression) results <> ";"
121+
122+data ConflictClause columns params where
123+ Conflict :: ConflictClause columns params
124+ OnConflictDoNothing :: ConflictClause columns params
125+ OnConflictDoUpdate
126+ :: NP (Aliased (UpdateExpression columns params)) columns
127+ -> Maybe (Condition '[table ::: columns] 'Ungrouped params)
128+ -> ConflictClause columns params
129+
130+renderConflictClause
131+ :: SListI columns
132+ => ConflictClause columns params
133+ -> ByteString
134+renderConflictClause = \case
135+ Conflict -> ""
136+ OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
137+ OnConflictDoUpdate updates whMaybe
138+ -> " ON CONFLICT DO UPDATE SET"
139+ <+> renderCommaSeparatedMaybe renderUpdateExpression updates
140+ <> case whMaybe of
141+ Nothing -> ""
142+ Just wh -> " WHERE" <+> renderExpression wh
143+
144+{-----------------------------------------
145+UPDATE statements
146+-----------------------------------------}
147+
148+data UpdateExpression columns params ty
149+ = Same
150+ | Set (forall table. Expression '[table ::: columns] 'Ungrouped params ty)
151+deriving instance Show (UpdateExpression columns params ty)
152+deriving instance Eq (UpdateExpression columns params ty)
153+deriving instance Ord (UpdateExpression columns params ty)
154+
155+renderUpdateExpression
156+ :: Aliased (UpdateExpression params columns) column
157+ -> Maybe ByteString
158+renderUpdateExpression = \case
159+ Same `As` _ -> Nothing
160+ Set expression `As` column -> Just $
161+ renderAlias column <+> "=" <+> renderExpression expression
162+
163+update
164+ :: (HasTable table schema columns, SListI columns, SListI results)
165+ => Alias table
166+ -> NP (Aliased (UpdateExpression columns params)) columns
167+ -> Condition '[tab ::: columns] 'Ungrouped params
168+ -> ReturningClause columns params results
169+ -> Manipulation schema params results
170+update table columns wh returning = UnsafeManipulation $
171+ "UPDATE"
172+ <+> renderAlias table
173+ <+> "SET"
174+ <+> renderCommaSeparatedMaybe renderUpdateExpression columns
175+ <+> "WHERE" <+> renderExpression wh
176+ <> renderReturningClause returning
177+
178+{-----------------------------------------
179+DELETE statements
180+-----------------------------------------}
181+
182+deleteFrom
183+ :: HasTable table schema columns
184+ => Alias table
185+ -> Condition '[table ::: columns] 'Ungrouped params
186+ -> Manipulation schema params '[]
187+deleteFrom table wh = UnsafeManipulation $
188+ "DELETE FROM" <+> renderAlias table
189+ <+> "WHERE" <+> renderExpression wh <> ";"
squeal-postgresql/src/Squeal/PostgreSQL/PQ.hs+24−8
@@ -14,6 +14,7 @@
1414 , ScopedTypeVariables
1515 , TypeApplications
1616 , TypeFamilies
17+ , TypeInType
1718 , TypeOperators
1819 , UndecidableInstances
1920#-}
@@ -27,6 +28,7 @@ import Control.Monad.Trans.Control
2728import Data.ByteString (ByteString)
2829import Data.Foldable
2930import Data.Function ((&))
31+import Data.Kind
3032import Data.Monoid
3133import Data.Traversable
3234import Generics.SOP
@@ -36,17 +38,23 @@ import GHC.TypeLits
3638import qualified Database.PostgreSQL.LibPQ as LibPQ
3739
3840import Squeal.PostgreSQL.Binary
39−import Squeal.PostgreSQL.Statement
41+import Squeal.PostgreSQL.Definition
42+import Squeal.PostgreSQL.Manipulation
43+import Squeal.PostgreSQL.Query
4044import Squeal.PostgreSQL.Schema
4145
42−newtype Connection (schema :: [(Symbol,[(Symbol,ColumnType)])]) =
46+-- | A `Connection` consists of a `Database.PastgreSQL.LibPQ.Connection`
47+-- and a phantom `Squeal.PostgreSQL.TablesType`
48+newtype Connection (schema :: TablesType) =
4349 Connection { unConnection :: LibPQ.Connection }
4450
51+-- | We keep track of the schema via an Atkey indexed state monad transformer,
52+-- `PQ`.
4553newtype PQ
46− (schema0 :: [(Symbol,[(Symbol,ColumnType)])])
47− (schema1 :: [(Symbol,[(Symbol,ColumnType)])])
48− (m :: * -> *)
49− (x :: *) =
54+ (schema0 :: TablesType)
55+ (schema1 :: TablesType)
56+ (m :: Type -> Type)
57+ (x :: Type) =
5058 PQ { runPQ :: Connection schema0 -> m (x, Connection schema1) }
5159 deriving Functor
5260
@@ -60,6 +68,7 @@ execPQ
6068 -> m (Connection schema1)
6169execPQ (PQ pq) = fmap snd . pq
6270
71+-- | indexed analog of `<*>`
6372pqAp
6473 :: Monad m
6574 => PQ schema0 schema1 m (x -> y)
@@ -70,6 +79,7 @@ pqAp (PQ f) (PQ x) = PQ $ \ conn -> do
7079 (x', conn'') <- x conn'
7180 return (f' x', conn'')
7281
82+-- | indexed analog of `=<<`
7383pqBind
7484 :: Monad m
7585 => (x -> PQ schema1 schema2 m y)
@@ -79,6 +89,7 @@ pqBind f (PQ x) = PQ $ \ conn -> do
7989 (x', conn') <- x conn
8090 runPQ (f x') conn'
8191
92+-- | indexed analog of `>>`
8293pqThen
8394 :: Monad m
8495 => PQ schema1 schema2 m y
@@ -86,6 +97,9 @@ pqThen
8697 -> PQ schema0 schema2 m y
8798pqThen pq2 pq1 = pq1 & pqBind (\ _ -> pq2)
8899
100+-- | Run a `Definition` with `LibPQ.exec`, we expect that libpq obeys the law
101+--
102+-- prop> then (define statement2) statement1 = define (statement2 . statement1)
89103define
90104 :: MonadBase IO io
91105 => Definition schema0 schema1
@@ -104,8 +118,10 @@ pqThenDefine
104118 -> PQ schema0 schema2 io (Result '[])
105119pqThenDefine = pqThen . define
106120
121+-- | `MonadPQ` is an `mtl` style constraint, similar to `MonadState`.
107122class Monad pq => MonadPQ schema pq | pq -> schema where
108−
123+ -- | `manipulateParams` runs a `Manipulation` with params from a type
124+ -- with a `ToParams` constraint
109125 manipulateParams
110126 :: ToParams x params
111127 => Manipulation schema params ys -> x -> pq (Result ys)
@@ -280,7 +296,7 @@ withConnection
280296 -> io x
281297withConnection connString = bracket (connectdb connString) finish
282298
283−newtype Result (xs :: [(Symbol,ColumnType)])
299+newtype Result (xs :: ColumnsType)
284300 = Result { unResult :: LibPQ.Result }
285301
286302newtype RowNumber = RowNumber { unRowNumber :: LibPQ.Row }
squeal-postgresql/src/Squeal/PostgreSQL/Prettyprint.hsnew file+50
@@ -0,0 +1,50 @@
1+{-# LANGUAGE
2+ MagicHash
3+ , OverloadedStrings
4+ , PolyKinds
5+ , RankNTypes
6+ , ScopedTypeVariables
7+ , TypeApplications
8+#-}
9+
10+module Squeal.PostgreSQL.Prettyprint where
11+
12+import Data.ByteString (ByteString)
13+import Data.Maybe
14+import Data.Monoid ((<>))
15+import Generics.SOP
16+import GHC.Exts
17+import GHC.TypeLits
18+
19+import qualified Data.ByteString as ByteString
20+
21+parenthesized :: ByteString -> ByteString
22+parenthesized str = "(" <> str <> ")"
23+
24+(<+>) :: ByteString -> ByteString -> ByteString
25+str1 <+> str2 = str1 <> " " <> str2
26+
27+commaSeparated :: [ByteString] -> ByteString
28+commaSeparated = ByteString.intercalate ", "
29+
30+renderCommaSeparated
31+ :: SListI xs
32+ => (forall x. expression x -> ByteString)
33+ -> NP expression xs -> ByteString
34+renderCommaSeparated render
35+ = commaSeparated
36+ . hcollapse
37+ . hmap (K . render)
38+
39+renderCommaSeparatedMaybe
40+ :: SListI xs
41+ => (forall x. expression x -> Maybe ByteString)
42+ -> NP expression xs -> ByteString
43+renderCommaSeparatedMaybe render
44+ = commaSeparated
45+ . catMaybes
46+ . hcollapse
47+ . hmap (K . render)
48+
49+renderNat :: KnownNat n => proxy n -> ByteString
50+renderNat (_ :: proxy n) = fromString (show (natVal' (proxy# :: Proxy# n)))
squeal-postgresql/src/Squeal/PostgreSQL/Query.hsnew file+393
@@ -0,0 +1,393 @@
1+{-# LANGUAGE
2+ DataKinds
3+ , DeriveDataTypeable
4+ , DeriveGeneric
5+ , FlexibleInstances
6+ , GADTs
7+ , GeneralizedNewtypeDeriving
8+ , KindSignatures
9+ , LambdaCase
10+ , MultiParamTypeClasses
11+ , OverloadedStrings
12+ , ScopedTypeVariables
13+ , StandaloneDeriving
14+ , TypeApplications
15+ , TypeInType
16+ , TypeOperators
17+ , UndecidableInstances
18+#-}
19+
20+module Squeal.PostgreSQL.Query
21+ ( -- * Queries
22+ Query (UnsafeQuery, renderQuery)
23+ , union
24+ , unionAll
25+ , intersect
26+ , intersectAll
27+ , except
28+ , exceptAll
29+ -- * SELECT queries
30+ , select
31+ , selectDistinct
32+ , selectStar
33+ , selectDistinctStar
34+ , selectDotStar
35+ , selectDistinctDotStar
36+ -- * FROM clauses
37+ , FromClause (..)
38+ , renderFromClause
39+ -- * Table Expressions
40+ , TableExpression (..)
41+ , renderTableExpression
42+ , from
43+ , where_
44+ , group
45+ , having
46+ , orderBy
47+ , limit
48+ , offset
49+ -- * Grouping
50+ , By (By, By2)
51+ , renderBy
52+ , GroupByClause (NoGroups, Group)
53+ , renderGroupByClause
54+ , HavingClause (NoHaving, Having)
55+ , renderHavingClause
56+ , GroupedBy (getGroup1, getGroup2)
57+ -- * Sorting
58+ , SortExpression (..)
59+ , renderSortExpression
60+ ) where
61+
62+import Control.DeepSeq
63+import Data.ByteString (ByteString)
64+import Data.Monoid
65+import Data.String
66+import Data.Word
67+import Generics.SOP hiding (from)
68+import GHC.TypeLits
69+
70+import qualified GHC.Generics as GHC
71+
72+import Squeal.PostgreSQL.Expression
73+import Squeal.PostgreSQL.Prettyprint
74+import Squeal.PostgreSQL.Schema
75+
76+newtype Query
77+ (schema :: TablesType)
78+ (params :: [ColumnType])
79+ (columns :: ColumnsType)
80+ = UnsafeQuery { renderQuery :: ByteString }
81+ deriving (GHC.Generic,Show,Eq,Ord,NFData)
82+
83+union, unionAll, intersect, intersectAll, except, exceptAll
84+ :: Query schema params columns
85+ -> Query schema params columns
86+ -> Query schema params columns
87+q1 `union` q2 = UnsafeQuery $
88+ parenthesized (renderQuery q1)
89+ <+> "UNION"
90+ <+> parenthesized (renderQuery q2)
91+q1 `unionAll` q2 = UnsafeQuery $
92+ parenthesized (renderQuery q1)
93+ <+> "UNION" <+> "ALL"
94+ <+> parenthesized (renderQuery q2)
95+q1 `intersect` q2 = UnsafeQuery $
96+ parenthesized (renderQuery q1)
97+ <+> "INTERSECT"
98+ <+> parenthesized (renderQuery q2)
99+q1 `intersectAll` q2 = UnsafeQuery $
100+ parenthesized (renderQuery q1)
101+ <+> "INTERSECT" <+> "ALL"
102+ <+> parenthesized (renderQuery q2)
103+q1 `except` q2 = UnsafeQuery $
104+ parenthesized (renderQuery q1)
105+ <+> "EXCEPT"
106+ <+> parenthesized (renderQuery q2)
107+q1 `exceptAll` q2 = UnsafeQuery $
108+ parenthesized (renderQuery q1)
109+ <+> "EXCEPT" <+> "ALL"
110+ <+> parenthesized (renderQuery q2)
111+
112+{-----------------------------------------
113+SELECT queries
114+-----------------------------------------}
115+
116+select
117+ :: SListI columns
118+ => NP (Aliased (Expression tables grouping params)) (column ': columns)
119+ -> TableExpression schema params tables grouping
120+ -> Query schema params (column ': columns)
121+select list tabs = UnsafeQuery $
122+ "SELECT"
123+ <+> renderCommaSeparated (renderAliased renderExpression) list
124+ <+> renderTableExpression tabs
125+
126+selectDistinct
127+ :: SListI columns
128+ => NP (Aliased (Expression tables 'Ungrouped params)) (column ': columns)
129+ -> TableExpression schema params tables 'Ungrouped
130+ -> Query schema params (column ': columns)
131+selectDistinct list tabs = UnsafeQuery $
132+ "SELECT DISTINCT"
133+ <+> renderCommaSeparated (renderAliased renderExpression) list
134+ <+> renderTableExpression tabs
135+
136+selectStar, selectDistinctStar
137+ :: HasUnique table tables columns
138+ => TableExpression schema params tables 'Ungrouped
139+ -> Query schema params columns
140+selectStar tabs = UnsafeQuery $ "SELECT" <+> "*" <+> renderTableExpression tabs
141+selectDistinctStar tabs = UnsafeQuery $
142+ "SELECT DISTINCT" <+> "*" <+> renderTableExpression tabs
143+
144+selectDotStar, selectDistinctDotStar
145+ :: HasTable table tables columns
146+ => Alias table
147+ -> TableExpression schema params tables 'Ungrouped
148+ -> Query schema params columns
149+selectDotStar table tables = UnsafeQuery $
150+ "SELECT" <+> renderAlias table <> ".*" <+> renderTableExpression tables
151+selectDistinctDotStar table tables = UnsafeQuery $
152+ "SELECT DISTINCT" <+> renderAlias table <> ".*"
153+ <+> renderTableExpression tables
154+
155+{-----------------------------------------
156+FROM clauses
157+-----------------------------------------}
158+
159+data FromClause schema params tables where
160+ Table
161+ :: Aliased (Table schema) table
162+ -> FromClause schema params '[table]
163+ Subquery
164+ :: Aliased (Query schema params) table
165+ -> FromClause schema params '[table]
166+ CrossJoin
167+ :: FromClause schema params right
168+ -> FromClause schema params left
169+ -> FromClause schema params (Join left right)
170+ InnerJoin
171+ :: FromClause schema params right
172+ -> Condition (Join left right) 'Ungrouped params
173+ -> FromClause schema params left
174+ -> FromClause schema params (Join left right)
175+ LeftOuterJoin
176+ :: FromClause schema params right
177+ -> Condition (Join left right) 'Ungrouped params
178+ -> FromClause schema params left
179+ -> FromClause schema params (Join left (NullifyTables right))
180+ RightOuterJoin
181+ :: FromClause schema params right
182+ -> Condition (Join left right) 'Ungrouped params
183+ -> FromClause schema params left
184+ -> FromClause schema params (Join (NullifyTables left) right)
185+ FullOuterJoin
186+ :: FromClause schema params right
187+ -> Condition (Join left right) 'Ungrouped params
188+ -> FromClause schema params left
189+ -> FromClause schema params
190+ (Join (NullifyTables left) (NullifyTables right))
191+
192+renderFromClause :: FromClause schema params tables -> ByteString
193+renderFromClause = \case
194+ Table table -> renderAliased renderTable table
195+ Subquery selection -> renderAliased (parenthesized . renderQuery) selection
196+ CrossJoin right left ->
197+ renderFromClause left <+> "CROSS JOIN" <+> renderFromClause right
198+ InnerJoin right on left -> renderJoin "INNER JOIN" right on left
199+ LeftOuterJoin right on left -> renderJoin "LEFT OUTER JOIN" right on left
200+ RightOuterJoin right on left -> renderJoin "RIGHT OUTER JOIN" right on left
201+ FullOuterJoin right on left -> renderJoin "FULL OUTER JOIN" right on left
202+ where
203+ renderJoin op right on left =
204+ renderFromClause left <+> op <+> renderFromClause right
205+ <+> "ON" <+> renderExpression on
206+
207+{-----------------------------------------
208+Table Expressions
209+-----------------------------------------}
210+
211+data TableExpression
212+ (schema :: TablesType)
213+ (params :: [ColumnType])
214+ (tables :: TablesType)
215+ (grouping :: Grouping)
216+ = TableExpression
217+ { fromClause :: FromClause schema params tables
218+ , whereClause :: [Condition tables 'Ungrouped params]
219+ , groupByClause :: GroupByClause tables grouping
220+ , havingClause :: HavingClause tables grouping params
221+ , orderByClause :: [SortExpression tables grouping params]
222+ , limitClause :: [Word64]
223+ , offsetClause :: [Word64]
224+ }
225+
226+renderTableExpression
227+ :: TableExpression schema params tables grouping
228+ -> ByteString
229+renderTableExpression
230+ (TableExpression tables whs' grps' hvs' srts' lims' offs') = mconcat
231+ [ "FROM ", renderFromClause tables
232+ , renderWheres whs'
233+ , renderGroupByClause grps'
234+ , renderHavingClause hvs'
235+ , renderSorts srts'
236+ , renderLimits lims'
237+ , renderOffsets offs'
238+ ]
239+ where
240+ renderWheres = \case
241+ [] -> ""
242+ wh:[] -> " WHERE" <+> renderExpression wh
243+ wh:whs -> " WHERE" <+> renderExpression (foldr (.&&) wh whs)
244+ renderSorts = \case
245+ [] -> ""
246+ srts -> " SORT BY"
247+ <+> commaSeparated (renderSortExpression <$> srts)
248+ renderLimits = \case
249+ [] -> ""
250+ lims -> " LIMIT" <+> fromString (show (minimum lims))
251+ renderOffsets = \case
252+ [] -> ""
253+ offs -> " OFFSET" <+> fromString (show (sum offs))
254+
255+from
256+ :: FromClause schema params tables
257+ -> TableExpression schema params tables 'Ungrouped
258+from tables = TableExpression tables [] NoGroups NoHaving [] [] []
259+
260+where_
261+ :: Condition tables 'Ungrouped params
262+ -> TableExpression schema params tables grouping
263+ -> TableExpression schema params tables grouping
264+where_ wh tables = tables {whereClause = wh : whereClause tables}
265+
266+group
267+ :: SListI bys
268+ => NP (By tables) bys
269+ -> TableExpression schema params tables 'Ungrouped
270+ -> TableExpression schema params tables ('Grouped bys)
271+group bys tables = TableExpression
272+ { fromClause = fromClause tables
273+ , whereClause = whereClause tables
274+ , groupByClause = Group bys
275+ , havingClause = Having []
276+ , orderByClause = []
277+ , limitClause = limitClause tables
278+ , offsetClause = offsetClause tables
279+ }
280+
281+having
282+ :: Condition tables ('Grouped bys) params
283+ -> TableExpression schema params tables ('Grouped bys)
284+ -> TableExpression schema params tables ('Grouped bys)
285+having hv tables = tables
286+ { havingClause = case havingClause tables of Having hvs -> Having (hv:hvs) }
287+
288+orderBy
289+ :: [SortExpression tables grouping params]
290+ -> TableExpression schema params tables grouping
291+ -> TableExpression schema params tables grouping
292+orderBy srts tables = tables {orderByClause = orderByClause tables ++ srts}
293+
294+limit
295+ :: Word64
296+ -> TableExpression schema params tables grouping
297+ -> TableExpression schema params tables grouping
298+limit lim tables = tables {limitClause = lim : limitClause tables}
299+
300+offset
301+ :: Word64
302+ -> TableExpression schema params tables grouping
303+ -> TableExpression schema params tables grouping
304+offset off tables = tables {offsetClause = off : offsetClause tables}
305+
306+{-----------------------------------------
307+Grouping
308+-----------------------------------------}
309+
310+data By
311+ (tables :: TablesType)
312+ (by :: (Symbol,Symbol)) where
313+ By
314+ :: (HasUnique table tables columns, HasColumn column columns ty)
315+ => Alias column
316+ -> By tables '(table, column)
317+ By2
318+ :: (HasTable table tables columns, HasColumn column columns ty)
319+ => (Alias table, Alias column)
320+ -> By tables '(table, column)
321+deriving instance Show (By tables by)
322+deriving instance Eq (By tables by)
323+deriving instance Ord (By tables by)
324+
325+renderBy :: By tables tabcolty -> ByteString
326+renderBy = \case
327+ By column -> renderAlias column
328+ By2 (table, column) -> renderAlias table <> "." <> renderAlias column
329+
330+data GroupByClause tables grouping where
331+ NoGroups :: GroupByClause tables 'Ungrouped
332+ Group
333+ :: SListI bys
334+ => NP (By tables) bys
335+ -> GroupByClause tables ('Grouped bys)
336+
337+renderGroupByClause :: GroupByClause tables grouping -> ByteString
338+renderGroupByClause = \case
339+ NoGroups -> ""
340+ Group Nil -> ""
341+ Group bys -> " GROUP BY" <+> renderCommaSeparated renderBy bys
342+
343+data HavingClause tables grouping params where
344+ NoHaving :: HavingClause tables 'Ungrouped params
345+ Having
346+ :: [Condition tables ('Grouped bys) params]
347+ -> HavingClause tables ('Grouped bys) params
348+deriving instance Show (HavingClause tables grouping params)
349+deriving instance Eq (HavingClause tables grouping params)
350+deriving instance Ord (HavingClause tables grouping params)
351+
352+renderHavingClause :: HavingClause tables grouping params -> ByteString
353+renderHavingClause = \case
354+ NoHaving -> ""
355+ Having [] -> ""
356+ Having conditions ->
357+ " HAVING" <+> commaSeparated (renderExpression <$> conditions)
358+
359+{-----------------------------------------
360+Sorting
361+-----------------------------------------}
362+
363+data SortExpression tables grouping params where
364+ Asc
365+ :: Expression tables grouping params ('Required ('NotNull ty))
366+ -> SortExpression tables grouping params
367+ Desc
368+ :: Expression tables grouping params ('Required ('NotNull ty))
369+ -> SortExpression tables grouping params
370+ AscNullsFirst
371+ :: Expression tables grouping params ('Required ('Null ty))
372+ -> SortExpression tables grouping params
373+ AscNullsLast
374+ :: Expression tables grouping params ('Required ('Null ty))
375+ -> SortExpression tables grouping params
376+ DescNullsFirst
377+ :: Expression tables grouping params ('Required ('Null ty))
378+ -> SortExpression tables grouping params
379+ DescNullsLast
380+ :: Expression tables grouping params ('Required ('Null ty))
381+ -> SortExpression tables grouping params
382+deriving instance Show (SortExpression tables grouping params)
383+
384+renderSortExpression :: SortExpression tables grouping params -> ByteString
385+renderSortExpression = \case
386+ Asc expression -> renderExpression expression <+> "ASC"
387+ Desc expression -> renderExpression expression <+> "DESC"
388+ AscNullsFirst expression -> renderExpression expression
389+ <+> "ASC NULLS FIRST"
390+ DescNullsFirst expression -> renderExpression expression
391+ <+> "DESC NULLS FIRST"
392+ AscNullsLast expression -> renderExpression expression <+> "ASC NULLS LAST"
393+ DescNullsLast expression -> renderExpression expression <+> "DESC NULLS LAST"
squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+197−72
@@ -1,5 +1,16 @@
1+{-|
2+Module: Squeal.PostgreSQL.Schema
3+Description: Embedding of PostgreSQL type and alias system
4+Copyright: (c) Eitan Chatav, 2017
5+Maintainer: eitan@morphism.tech
6+Stability: experimental
7+
8+Embedding of PostgreSQL type and alias system
9+-}
10+
111{-# LANGUAGE
2− DataKinds
12+ ConstraintKinds
13+ , DataKinds
314 , DeriveAnyClass
415 , DeriveDataTypeable
516 , DeriveGeneric
@@ -15,102 +26,160 @@
1526 , StandaloneDeriving
1627 , TypeApplications
1728 , TypeFamilies
29+ , TypeInType
1830 , TypeOperators
1931#-}
2032
2133module Squeal.PostgreSQL.Schema
22− ( PGType (..)
23− , PGNum (..)
34+ ( -- * Kinds
35+ PGType (..)
36+ , NullityType (..)
37+ , ColumnType (..)
38+ , ColumnsType
39+ , TablesType
40+ , Grouping (..)
41+ -- * Constraints
42+ , PGNum
2443 , PGIntegral
2544 , PGFloating
45+ -- * Aliases
2646 , (:::)
2747 , Alias (Alias)
2848 , renderAlias
2949 , Aliased (As)
3050 , renderAliased
51+ , IsLabel (..)
3152 , IsTableColumn (..)
32− , NullityType (..)
33− , ColumnType (..)
34− , Grouping (..)
53+ -- * Type Families
54+ , In
55+ , HasUnique
56+ , BaseType
3557 , SameTypes
3658 , AllNotNull
3759 , NotAllNull
3860 , NullifyType
3961 , NullifyColumns
4062 , NullifyTables
41− , BaseType
63+ , Join
4264 , Create
4365 , Drop
4466 , Alter
4567 , Rename
46− , Join
47− , module GHC.OverloadedLabels
48− , module GHC.TypeLits
68+ -- * Generics
69+ , SameField
70+ , SameFields
4971 ) where
5072
5173import Control.DeepSeq
5274import Data.ByteString
53−import Data.Data (Data)
5475import Data.Monoid
5576import Data.String
77+import Generics.SOP (AllZip)
5678import GHC.Generics (Generic)
5779import GHC.Exts
5880import GHC.OverloadedLabels
5981import GHC.TypeLits
6082
83+import qualified Generics.SOP.Type.Metadata as Type
84+
85+-- | `PGType` is the promoted datakind of PostgreSQL types.
6186data PGType
62− = PGbool
63− | PGint2
64− | PGint4
65− | PGint8
66− | PGnumeric
67− | PGfloat4
68− | PGfloat8
69− | PGchar Nat
70− | PGvarchar Nat
71− | PGtext
72− | PGbytea
73− | PGtimestamp
74− | PGtimestamptz
75− | PGdate
76− | PGtime
77− | PGtimetz
78− | PGinterval
79− | PGuuid
80− | PGinet
81− | PGjson
82− | PGjsonb
83−
84−class PGNum (ty :: PGType) where
85− decimal :: proxy ty -> Bool
86−instance PGNum 'PGint2 where decimal _ = False
87−instance PGNum 'PGint4 where decimal _ = False
88−instance PGNum 'PGint8 where decimal _ = False
89−instance PGNum 'PGnumeric where decimal _ = True
90−instance PGNum 'PGfloat4 where decimal _ = True
91−instance PGNum 'PGfloat8 where decimal _ = True
92−
93−class PGNum ty => PGIntegral (ty :: PGType) where
94−instance PGIntegral 'PGint2
95−instance PGIntegral 'PGint4
96−instance PGIntegral 'PGint8
97−
98−class PGNum ty => PGFloating (ty :: PGType) where
99−instance PGFloating 'PGnumeric
100−instance PGFloating 'PGfloat4
101−instance PGFloating 'PGfloat8
87+ = PGbool -- ^ logical Boolean (true/false)
88+ | PGint2 -- ^ signed two-byte integer
89+ | PGint4 -- ^ signed four-byte integer
90+ | PGint8 -- ^ signed eight-byte integer
91+ | PGnumeric -- ^ arbitrary precision numeric type
92+ | PGfloat4 -- ^ single precision floating-point number (4 bytes)
93+ | PGfloat8 -- ^ double precision floating-point number (8 bytes)
94+ | PGchar Nat -- ^ fixed-length character string
95+ | PGvarchar Nat -- ^ variable-length character string
96+ | PGtext -- ^ variable-length character string
97+ | PGbytea -- ^ binary data ("byte array")
98+ | PGtimestamp -- ^ date and time (no time zone)
99+ | PGtimestamptz -- ^ date and time, including time zone
100+ | PGdate -- ^ calendar date (year, month, day)
101+ | PGtime -- ^ time of day (no time zone)
102+ | PGtimetz -- ^ time of day, including time zone
103+ | PGinterval -- ^ time span
104+ | PGuuid -- ^ universally unique identifier
105+ | PGinet -- ^ IPv4 or IPv6 host address
106+ | PGjson -- ^ textual JSON data
107+ | PGjsonb -- ^ binary JSON data, decomposed
108+
109+-- | `NullityType` encodes the potential presence or definite absence of a
110+-- @NULL@ allowing operations which are sensitive to such to be well typed.
111+data NullityType
112+ = Null PGType -- ^ @NULL@ may be present
113+ | NotNull PGType -- ^ @NULL@ is absent
114+
115+-- | `ColumnType` encodes the allowance of @DEFAULT@ and the only way
116+-- to generate an `Optional` `Squeal.PostgreSQL.Expression.Expression`
117+-- is to use `Squeal.PostgreSQL.Expression.def`,
118+-- `Squeal.PostgreSQL.Expression.unDef` or
119+-- `Squeal.PostgreSQL.Expression.param`.
120+data ColumnType
121+ = Optional NullityType
122+ -- ^ @DEFAULT@ is allowed
123+ | Required NullityType
124+ -- ^ @DEFAULT@ is not allowed
125+
126+-- | `ColumnsType` is a kind synonym for a row of `ColumnType`s.
127+type ColumnsType = [(Symbol,ColumnType)]
128+
129+-- | `TablesType` is a kind synonym for a row of `ColumnsType`s.
130+-- It is used as a kind for both a schema, a disjoint union of tables,
131+-- and a joined table `Squeal.Query.FromClause`, a product of tables.
132+type TablesType = [(Symbol,ColumnsType)]
133+
134+-- | `Grouping` is an auxiliary namespace, created by
135+-- @GROUP BY@ clauses (`Squeal.PostgreSQL.Query.group`), and used
136+-- for typesafe aggregation
137+data Grouping
138+ = Ungrouped
139+ | Grouped [(Symbol,Symbol)]
102140
141+-- | `PGNum` is a constraint on `PGType` whose
142+-- `Squeal.PostgreSQL.Expression.Expression`s have a `Num` constraint.
143+type PGNum ty =
144+ In ty '[ 'PGint2, 'PGint4, 'PGint8, 'PGnumeric, 'PGfloat4, 'PGfloat8]
145+
146+-- | `PGFloating` is a constraint on `PGType` whose
147+-- `Squeal.PostgreSQL.Expression.Expression`s
148+-- have `Fractional` and `Floating` constraints.
149+type PGFloating ty = In ty '[ 'PGfloat4, 'PGfloat8, 'PGnumeric]
150+
151+-- | `PGIntegral` is a constraint on `PGType` whose
152+-- `Squeal.PostgreSQL.Expression.Expression`s
153+-- have `Squeal.PostgreSQL.Expression.div_` and
154+-- `Squeal.PostgreSQL.Expression.mod_` functions.
155+type PGIntegral ty = In ty '[ 'PGint2, 'PGint4, 'PGint8]
156+
157+-- | `:::` is the promoted version of `As`, a type level pair between
158+-- an alias and some type, usually a column alias and a `ColumnType` or
159+-- a table alias and a `ColumnsType`.
103160type (:::) (alias :: Symbol) (ty :: polykind) = '(alias,ty)
104161
162+-- | `Alias`es are proxies for a type level string or `Symbol`
163+-- and have an `IsLabel` instance so that with @-XOverloadedLabels@
164+--
165+-- >>> :set -XOverloadedLabels
166+-- >>> #foobar :: Alias "foobar"
167+-- Alias
105168data Alias (alias :: Symbol) = Alias
106− deriving (Eq,Generic,Data,Ord,Show,NFData)
169+ deriving (Eq,Generic,Ord,Show,NFData)
170+instance alias1 ~ alias2 => IsLabel alias1 (Alias alias2) where
171+ fromLabel = Alias
107172
173+-- | >>> renderAlias #alias
174+-- "alias"
108175renderAlias :: KnownSymbol alias => Alias alias -> ByteString
109176renderAlias = fromString . symbolVal
110177
111−instance alias1 ~ alias2 => IsLabel alias1 (Alias alias2) where
112− fromLabel = Alias
113−
178+-- | The `As` operator is used to name an expression. `As` is the demoted
179+-- version of `:::`.
180+--
181+-- >>> Just "hello" `As` #hi :: Aliased Maybe ("hi" ::: String)
182+-- As (Just "hello") Alias
114183data Aliased expression aliased where
115184 As
116185 :: KnownSymbol alias
@@ -124,6 +193,9 @@ deriving instance Eq (expression ty)
124193deriving instance Ord (expression ty)
125194 => Ord (Aliased expression (alias ::: ty))
126195
196+-- | >>> let renderMaybe = fromString . maybe "Nothing" (const "Just")
197+-- >>> renderAliased renderMaybe (Just (3::Int) `As` #an_int)
198+-- "Just AS an_int"
127199renderAliased
128200 :: (forall ty. expression ty -> ByteString)
129201 -> Aliased expression aliased
@@ -131,67 +203,120 @@ renderAliased
131203renderAliased render (expression `As` alias) =
132204 render expression <> " AS " <> renderAlias alias
133205
206+-- | Analagous to `IsLabel`, the constraint
207+-- `IsTableColumn` defines `!` for a column alias qualified
208+-- by a table alias.
134209class IsTableColumn table column expression where
135210 (!) :: Alias table -> Alias column -> expression
136211 infixl 9 !
137212instance IsTableColumn table column (Alias table, Alias column) where (!) = (,)
138213
139−data NullityType = Null PGType | NotNull PGType
214+-- | `In x xs` is a constraint that proves that `x` is in `xs.
215+type family In x xs :: Constraint where
216+ In x (x ': xs) = ()
217+ In x (y ': xs) = In x xs
140218
141−data ColumnType = Optional NullityType | Required NullityType
142−
143−data Grouping
144− = Ungrouped
145− | Grouped [(Symbol,Symbol)]
219+-- | `HasUnique alias xs x` is a constraint that proves that xs is a singleton
220+-- of `alias ::: x`.
221+type HasUnique alias xs x = xs ~ '[alias ::: x]
146222
147−type family BaseType (ty :: ColumnType) where
223+-- | `BaseType` forgets about @NULL@ and @DEFAULT@
224+type family BaseType (ty :: ColumnType) :: PGType where
148225 BaseType (optionality (nullity pg)) = pg
149226
150−type family SameTypes columns0 columns1 :: Constraint where
227+-- | `SameTypes` is a constraint that proves two `ColumnsType`s have the same
228+-- length and the same `ColumnType`s.
229+type family SameTypes
230+ (columns0 :: ColumnsType) (columns1 :: ColumnsType)
231+ :: Constraint where
151232 SameTypes '[] '[] = ()
152233 SameTypes ((column0 ::: ty0) ': columns0) ((column1 ::: ty1) ': columns1)
153234 = (ty0 ~ ty1, SameTypes columns0 columns1)
154235
155−type family AllNotNull columns :: Constraint where
236+-- | `AllNotNull` is a constraint that proves a `ColumnsType` has no @NULL@s.
237+type family AllNotNull (columns :: ColumnsType) :: Constraint where
156238 AllNotNull '[] = ()
157239 AllNotNull ((column ::: (optionality ('NotNull ty))) ': columns)
158240 = AllNotNull columns
159241
242+-- | `NotAllNull` is a constraint that proves a `ColumnsType` has some
243+-- @NOT NULL@.
160244type family NotAllNull columns :: Constraint where
161245 NotAllNull ((column ::: (optionality ('NotNull ty))) ': columns) = ()
162246 NotAllNull ((column ::: (optionality ('Null ty))) ': columns)
163247 = NotAllNull columns
164248
165−type family NullifyType (ty :: ColumnType) where
249+-- | `NullifyType` is an idempotent that nullifies a `ColumnType`.
250+type family NullifyType (ty :: ColumnType) :: ColumnType where
166251 NullifyType (optionality ('Null ty)) = optionality ('Null ty)
167252 NullifyType (optionality ('NotNull ty)) = optionality ('Null ty)
168253
169−type family NullifyColumns columns where
254+-- | `NullifyColumns` is an idempotent that nullifies a `ColumnsType`.
255+type family NullifyColumns (columns :: ColumnsType) :: ColumnsType where
170256 NullifyColumns '[] = '[]
171257 NullifyColumns ((column ::: ty) ': columns) =
172258 (column ::: NullifyType ty) ': NullifyColumns columns
173259
174−type family NullifyTables tables where
260+-- | `NullifyTables` is an idempotent that nullifies a `TablesType`
261+-- used to nullify the left or right hand side of an outer join
262+-- in a `Squeal.PostgreSQL.Query.FromClause`
263+type family NullifyTables (tables :: TablesType) :: TablesType where
175264 NullifyTables '[] = '[]
176− NullifyTables ((column ::: ty) ': columns) =
177− (column ::: NullifyColumns ty) ': NullifyTables columns
265+ NullifyTables ((table ::: columns) ': tables) =
266+ (table ::: NullifyColumns columns) ': NullifyTables tables
267+
268+-- | `Join` is simply promoted `++` and is used in @JOIN@s in
269+-- `Squeal.PostgreSQL.Query.FromClause`s.
270+type family Join xs ys where
271+ Join '[] ys = ys
272+ Join (x ': xs) ys = x ': Join xs ys
178273
274+-- | @Create alias x xs@ adds @alias ::: x@ to the end of @xs@ and is used in
275+-- `Squeal.PostgreSQL.Definition.createTable` statements and in @ALTER TABLE@
276+-- `Squeal.PostgreSQL.Definition.addColumnDefault` and
277+-- `Squeal.PostgreSQL.Definition.addColumnNull` statements.
179278type family Create alias x xs where
180279 Create alias x '[] = '[alias ::: x]
181280 Create alias y (x ': xs) = x ': Create alias y xs
182281
282+-- | @Drop alias xs@ removes the type associated with @alias@ in @xs@
283+-- and is used in `Squeal.PostgreSQL.Definition.dropTable` statements
284+-- and in @ALTER TABLE@ `Squeal.PostgreSQL.Definition.dropColumn` statements.
183285type family Drop alias xs where
184286 Drop alias ((alias ::: x) ': xs) = xs
185287 Drop alias (x ': xs) = x ': Drop alias xs
186288
289+-- | @Alter alias xs x@ replaces the type associated with an @alias@ in @xs@
290+-- with the type `x` and is used in `Squeal.PostgreSQL.Definition.alterTable`
291+-- and `Squeal.PostgreSQL.Definition.alterColumn`.
187292type family Alter alias xs x where
188293 Alter alias ((alias ::: x0) ': xs) x1 = (alias ::: x1) ': xs
189294 Alter alias (x0 ': xs) x1 = x0 ': Alter alias xs x1
190295
296+-- | @Rename alias0 alias1 xs@ replaces the alias @alias0@ by @alias1@ in @xs@
297+-- and is used in `Squeal.PostgreSQL.Definition.alterTableRename` and
298+-- `Squeal.PostgreSQL.Definition.renameColumn`.
191299type family Rename alias0 alias1 xs where
192300 Rename alias0 alias1 ((alias0 ::: x0) ': xs) = (alias1 ::: x0) ': xs
193301 Rename alias0 alias1 (x ': xs) = x ': Rename alias0 alias1 xs
194302
195−type family Join xs ys where
196− Join '[] ys = ys
197− Join (x ': xs) ys = x ': Join xs ys
303+-- | A `SameField` constraint is an equality constraint on a
304+-- `Generics.SOP.Type.Metadata.FieldInfo` and the column alias in a `:::` pair.
305+class SameField
306+ (fieldInfo :: Type.FieldInfo) (fieldty :: (Symbol,ColumnType)) where
307+instance field ~ column => SameField ('Type.FieldInfo field) (column ::: ty)
308+
309+-- | A `SameFields` constraint proves that a
310+-- `Generics.SOP.Type.Metadata.DatatypeInfo` of a record type has the same
311+-- field names as the column aliases of a `ColumnsType`.
312+type family SameFields
313+ (datatypeInfo :: Type.DatatypeInfo) (columns :: ColumnsType)
314+ :: Constraint where
315+ SameFields
316+ ('Type.ADT _module _datatype '[ 'Type.Record _constructor fields])
317+ columns
318+ = AllZip SameField fields columns
319+ SameFields
320+ ('Type.Newtype _module _datatype ('Type.Record _constructor fields))
321+ columns
322+ = AllZip SameField fields columns
squeal-postgresql/src/Squeal/PostgreSQL/Statement.hsdeleted−1447
@@ -1,1447 +0,0 @@
1−{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
2−{-# LANGUAGE
3− DataKinds
4− , DeriveGeneric
5− , DeriveDataTypeable
6− , FlexibleContexts
7− , FlexibleInstances
8− , FunctionalDependencies
9− , GADTs
10− , GeneralizedNewtypeDeriving
11− , LambdaCase
12− , MagicHash
13− , OverloadedStrings
14− , PolyKinds
15− , RankNTypes
16− , ScopedTypeVariables
17− , StandaloneDeriving
18− , TypeApplications
19− , TypeFamilies
20− , TypeOperators
21− , UndecidableInstances
22−#-}
23−
24−module Squeal.PostgreSQL.Statement where
25−
26−import Control.Category
27−import Control.DeepSeq
28−import Data.ByteString (ByteString)
29−import Data.Data (Data)
30−import Data.Foldable
31−import Data.Maybe
32−import Data.Monoid hiding (All)
33−import Data.Ratio
34−import Data.String
35−import Data.Word
36−import Generics.SOP
37−import GHC.Exts
38−import GHC.OverloadedLabels
39−import GHC.TypeLits
40−import Prelude hiding (RealFrac(..), id, (.))
41−
42−import qualified GHC.Generics as GHC
43−import qualified Data.ByteString as ByteString
44−
45−import Squeal.PostgreSQL.Schema
46−
47−{-----------------------------------------
48−column expressions
49−-----------------------------------------}
50−
51−newtype Expression
52− (params :: [ColumnType])
53− (tables :: [(Symbol,[(Symbol,ColumnType)])])
54− (grouping :: Grouping)
55− (ty :: ColumnType)
56− = UnsafeExpression { renderExpression :: ByteString }
57− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
58−
59−class (PGTyped (BaseType ty), KnownNat n)
60− => HasParameter (n :: Nat) params ty | n params -> ty where
61− param :: proxy n -> Expression params tables grouping ty
62− param _ = UnsafeExpression $ parenthesized $
63− "$" <> renderNat (Proxy @n) <+> "::"
64− <+> renderTypeExpression (pgtype @(BaseType ty))
65−instance {-# OVERLAPPING #-} PGTyped (BaseType ty1)
66− => HasParameter 1 (ty1:tys) ty1
67−instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) params ty)
68− => HasParameter n (ty' : params) ty
69−
70−class KnownSymbol column => HasColumn column columns ty
71− | column columns -> ty where
72− getColumn
73− :: Alias column
74− -> Expression params '[table ::: columns] 'Ungrouped ty
75− getColumn column = UnsafeExpression $ renderAlias column
76−instance {-# OVERLAPPING #-} KnownSymbol column
77− => HasColumn column ((column ::: optionality ty) ': tys) ('Required ty)
78−instance {-# OVERLAPPABLE #-} (KnownSymbol column, HasColumn column table ty)
79− => HasColumn column (ty' ': table) ty
80−
81−instance (HasColumn column columns ty, tables ~ '[table ::: columns])
82− => IsLabel column (Expression params tables 'Ungrouped ty) where
83− fromLabel = getColumn (Alias @column)
84−
85−instance (HasTable table tables columns, HasColumn column columns ty)
86− => IsTableColumn table column (Expression params tables 'Ungrouped ty) where
87− table ! column = UnsafeExpression $
88− renderAlias table <> "." <> renderAlias column
89−
90−data Column
91− (columns :: [(Symbol,ColumnType)])
92− (columnty :: (Symbol,ColumnType))
93− where
94− Column
95− :: HasColumn column columns ty
96− => Alias column
97− -> Column columns (column ::: ty)
98−deriving instance Show (Column columns columnty)
99−deriving instance Eq (Column columns columnty)
100−deriving instance Ord (Column columns columnty)
101−
102−def :: Expression params '[] 'Ungrouped ('Optional (nullity ty))
103−def = UnsafeExpression "DEFAULT"
104−
105−unDef
106− :: Expression params '[] 'Ungrouped ('Required (nullity ty))
107− -> Expression params '[] 'Ungrouped ('Optional (nullity ty))
108−unDef = UnsafeExpression . renderExpression
109−
110−null :: Expression params tables grouping (optionality ('Null ty))
111−null = UnsafeExpression "NULL"
112−
113−unNull
114− :: Expression params tables grouping (optionality ('NotNull ty))
115− -> Expression params tables grouping (optionality ('Null ty))
116−unNull = UnsafeExpression . renderExpression
117−
118−coalesce
119− :: [Expression params tables grouping ('Required ('Null ty))]
120− -> Expression params tables grouping ('Required ('NotNull ty))
121− -> Expression params tables grouping ('Required ('NotNull ty))
122−coalesce nullxs notNullx = UnsafeExpression $
123− "COALESCE" <> parenthesized (commaSeparated
124− ((renderExpression <$> nullxs) <> [renderExpression notNullx]))
125−
126−fromNull
127− :: Expression params tables grouping ('Required ('NotNull ty))
128− -> Expression params tables grouping ('Required ('Null ty))
129− -> Expression params tables grouping ('Required ('NotNull ty))
130−fromNull notNullx nullx = coalesce [nullx] notNullx
131−
132−isNull
133− :: Expression params tables grouping ('Required ('Null ty))
134− -> Condition params tables grouping
135−isNull x = UnsafeExpression $ renderExpression x <+> "IS NULL"
136−
137−isn'tNull
138− :: Expression params tables grouping ('Required ('Null ty))
139− -> Condition params tables grouping
140−isn'tNull x = UnsafeExpression $ renderExpression x <+> "IS NOT NULL"
141−
142−matchNull
143− :: Expression params tables grouping ('Required nullty)
144− -> ( Expression params tables grouping ('Required ('NotNull ty))
145− -> Expression params tables grouping ('Required nullty) )
146− -> Expression params tables grouping ('Required ('Null ty))
147− -> Expression params tables grouping ('Required nullty)
148−matchNull y f x = ifThenElse (isNull x) y
149− (f (UnsafeExpression (renderExpression y)))
150−
151−nullIf
152− :: Expression params tables grouping ('Required ('NotNull ty))
153− -> Expression params tables grouping ('Required ('NotNull ty))
154− -> Expression params tables grouping ('Required ('Null ty))
155−nullIf x y = UnsafeExpression $ parenthesized $
156− renderExpression x <> ", " <> renderExpression y
157−
158−greatest
159− :: [Expression params tables grouping ('Required nullty)]
160− -> Expression params table grouping ('Required nullty)
161−greatest xs = UnsafeExpression $ "GREATEST("
162− <> commaSeparated (renderExpression <$> xs) <> ")"
163−
164−least
165− :: [Expression params tables grouping ('Required nullty)]
166− -> Expression params table grouping ('Required nullty)
167−least xs = UnsafeExpression $ "LEAST("
168− <> commaSeparated (renderExpression <$> xs) <> ")"
169−
170−unsafeBinaryOp
171− :: ByteString
172− -> Expression params tables grouping ('Required ty0)
173− -> Expression params tables grouping ('Required ty1)
174− -> Expression params tables grouping ('Required ty2)
175−unsafeBinaryOp op x y = UnsafeExpression $ parenthesized $
176− renderExpression x <+> op <+> renderExpression y
177−
178−unsafeUnaryOp
179− :: ByteString
180− -> Expression params tables grouping ('Required ty0)
181− -> Expression params tables grouping ('Required ty1)
182−unsafeUnaryOp op x = UnsafeExpression $ parenthesized $
183− op <+> renderExpression x
184−
185−unsafeFunction
186− :: ByteString
187− -> Expression params tables grouping ('Required xty)
188− -> Expression params tables grouping ('Required yty)
189−unsafeFunction fun x = UnsafeExpression $
190− fun <> parenthesized (renderExpression x)
191−
192−instance PGNum ty
193− => Num (Expression params tables grouping ('Required (nullity ty))) where
194− (+) = unsafeBinaryOp "+"
195− (-) = unsafeBinaryOp "-"
196− (*) = unsafeBinaryOp "*"
197− abs = unsafeFunction "abs"
198− signum = unsafeFunction "sign"
199− fromInteger
200− = UnsafeExpression
201− . (<> if decimal (Proxy :: Proxy ty) then "." else "")
202− . fromString
203− . show
204−
205−instance PGFloating ty => Fractional
206− (Expression params tables grouping ('Required (nullity ty))) where
207− (/) = unsafeBinaryOp "/"
208− fromRational x = fromInteger (numerator x) / fromInteger (denominator x)
209−
210−instance (PGFloating ty, PGTyped ty) => Floating
211− (Expression params tables grouping ('Required (nullity ty))) where
212− pi = UnsafeExpression "pi()"
213− exp = unsafeFunction "exp"
214− log = unsafeFunction "ln"
215− sqrt = unsafeFunction "sqrt"
216− b ** x = UnsafeExpression $
217− "power(" <> renderExpression b <> ", " <> renderExpression x <> ")"
218− logBase b y = cast pgtype $ logBaseNumeric b y
219− where
220− logBaseNumeric
221− :: Expression params tables grouping ('Required (nullity ty))
222− -> Expression params tables grouping ('Required (nullity ty))
223− -> Expression params tables grouping ('Required (nullity 'PGnumeric))
224− logBaseNumeric b' y' = UnsafeExpression $ mconcat
225− [ "log("
226− , renderExpression (cast numeric b')
227− , ", "
228− , renderExpression (cast numeric y')
229− , ")"
230− ]
231− sin = unsafeFunction "sin"
232− cos = unsafeFunction "cos"
233− tan = unsafeFunction "tan"
234− asin = unsafeFunction "asin"
235− acos = unsafeFunction "acos"
236− atan = unsafeFunction "atan"
237− sinh x = (exp x - exp (-x)) / 2
238− cosh x = (exp x + exp (-x)) / 2
239− tanh x = sinh x / cosh x
240− asinh x = log (x + sqrt (x*x + 1))
241− acosh x = log (x + sqrt (x*x - 1))
242− atanh x = log ((1 + x) / (1 - x)) / 2
243−
244−atan2
245− :: PGFloating float
246− => Expression params tables grouping ('Required (nullity float))
247− -> Expression params tables grouping ('Required (nullity float))
248− -> Expression params tables grouping ('Required (nullity float))
249−atan2 y x = UnsafeExpression $
250− "atan2(" <> renderExpression y <> ", " <> renderExpression x <> ")"
251−
252−cast
253− :: TypeExpression ('Required ('Null ty1))
254− -> Expression params tables grouping ('Required (nullity ty0))
255− -> Expression params tables grouping ('Required (nullity ty1))
256−cast ty x = UnsafeExpression $ parenthesized $
257− renderExpression x <+> "::" <+> renderTypeExpression ty
258−
259−div
260− :: PGIntegral int
261− => Expression params tables grouping ('Required (nullity int))
262− -> Expression params tables grouping ('Required (nullity int))
263− -> Expression params tables grouping ('Required (nullity int))
264−div = unsafeBinaryOp "/"
265−
266−mod
267− :: PGIntegral int
268− => Expression params tables grouping ('Required (nullity int))
269− -> Expression params tables grouping ('Required (nullity int))
270− -> Expression params tables grouping ('Required (nullity int))
271−mod = unsafeBinaryOp "%"
272−
273−trunc
274− :: PGFloating frac
275− => Expression params tables grouping ('Required (nullity frac))
276− -> Expression params tables grouping ('Required (nullity frac))
277−trunc = unsafeFunction "trunc"
278−
279−round_
280− :: PGFloating frac
281− => Expression params tables grouping ('Required (nullity frac))
282− -> Expression params tables grouping ('Required (nullity frac))
283−round_ = unsafeFunction "round"
284−
285−ceiling_
286− :: PGFloating frac
287− => Expression params tables grouping ('Required (nullity frac))
288− -> Expression params tables grouping ('Required (nullity frac))
289−ceiling_ = unsafeFunction "ceiling"
290−
291−instance IsString
292− (Expression params tables grouping ('Required (nullity 'PGtext))) where
293− fromString str = UnsafeExpression $
294− "E\'" <> fromString (escape =<< str) <> "\'"
295− where
296− escape = \case
297− '\NUL' -> "\\0"
298− '\'' -> "''"
299− '"' -> "\\\""
300− '\b' -> "\\b"
301− '\n' -> "\\n"
302− '\r' -> "\\r"
303− '\t' -> "\\t"
304− '\\' -> "\\\\"
305− c -> [c]
306−
307−type Condition params tables grouping =
308− Expression params tables grouping ('Required ('NotNull 'PGbool))
309−
310−true :: Condition params tables grouping
311−true = UnsafeExpression "TRUE"
312−
313−false :: Condition params tables grouping
314−false = UnsafeExpression "FALSE"
315−
316−not_
317− :: Condition params tables grouping
318− -> Condition params tables grouping
319−not_ = unsafeUnaryOp "NOT"
320−
321−(&&*)
322− :: Condition params tables grouping
323− -> Condition params tables grouping
324− -> Condition params tables grouping
325−(&&*) = unsafeBinaryOp "AND"
326−
327−(||*)
328− :: Condition params tables grouping
329− -> Condition params tables grouping
330− -> Condition params tables grouping
331−(||*) = unsafeBinaryOp "OR"
332−
333−caseWhenThenElse
334− :: [ ( Condition params tables grouping
335− , Expression params tables grouping ('Required ty)
336− ) ]
337− -> Expression params tables grouping ('Required ty)
338− -> Expression params tables grouping ('Required ty)
339−caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
340− [ "CASE"
341− , mconcat
342− [ mconcat
343− [ " WHEN ", renderExpression when_
344− , " THEN ", renderExpression then_
345− ]
346− | (when_,then_) <- whenThens
347− ]
348− , " ELSE ", renderExpression else_
349− , " END"
350− ]
351−
352−ifThenElse
353− :: Condition params tables grouping
354− -> Expression params tables grouping ('Required ty)
355− -> Expression params tables grouping ('Required ty)
356− -> Expression params tables grouping ('Required ty)
357−ifThenElse if_ then_ else_ = caseWhenThenElse [(if_,then_)] else_
358−
359−(==*)
360− :: Expression params tables grouping ('Required (nullity ty))
361− -> Expression params tables grouping ('Required (nullity ty))
362− -> Expression params tables grouping ('Required (nullity 'PGbool))
363−(==*) = unsafeBinaryOp "="
364−infix 4 ==*
365−
366−(/=*)
367− :: Expression params tables grouping ('Required (nullity ty))
368− -> Expression params tables grouping ('Required (nullity ty))
369− -> Expression params tables grouping ('Required (nullity 'PGbool))
370−(/=*) = unsafeBinaryOp "<>"
371−infix 4 /=*
372−
373−(>=*)
374− :: Expression params tables grouping ('Required (nullity ty))
375− -> Expression params tables grouping ('Required (nullity ty))
376− -> Expression params tables grouping ('Required (nullity 'PGbool))
377−(>=*) = unsafeBinaryOp ">="
378−infix 4 >=*
379−
380−(<*)
381− :: Expression params tables grouping ('Required (nullity ty))
382− -> Expression params tables grouping ('Required (nullity ty))
383− -> Expression params tables grouping ('Required (nullity 'PGbool))
384−(<*) = unsafeBinaryOp "<"
385−infix 4 <*
386−
387−(<=*)
388− :: Expression params tables grouping ('Required (nullity ty))
389− -> Expression params tables grouping ('Required (nullity ty))
390− -> Expression params tables grouping ('Required (nullity 'PGbool))
391−(<=*) = unsafeBinaryOp "<="
392−infix 4 <=*
393−
394−(>*)
395− :: Expression params tables grouping ('Required (nullity ty))
396− -> Expression params tables grouping ('Required (nullity ty))
397− -> Expression params tables grouping ('Required (nullity 'PGbool))
398−(>*) = unsafeBinaryOp ">"
399−infix 4 >*
400−
401−currentDate
402− :: Expression params tables grouping ('Required (nullity 'PGdate))
403−currentDate = UnsafeExpression "CURRENT_DATE"
404−
405−currentTime
406− :: Expression params tables grouping ('Required (nullity 'PGtimetz))
407−currentTime = UnsafeExpression "CURRENT_TIME"
408−
409−currentTimestamp
410− :: Expression params tables grouping ('Required (nullity 'PGtimestamptz))
411−currentTimestamp = UnsafeExpression "CURRENT_TIMESTAMP"
412−
413−localTime
414− :: Expression params tables grouping ('Required (nullity 'PGtime))
415−localTime = UnsafeExpression "LOCALTIME"
416−
417−localTimestamp
418− :: Expression params tables grouping ('Required (nullity 'PGtimestamp))
419−localTimestamp = UnsafeExpression "LOCALTIMESTAMP"
420−
421−data SortExpression params tables grouping where
422− Asc
423− :: Expression params tables grouping ('Required ('NotNull ty))
424− -> SortExpression params tables grouping
425− Desc
426− :: Expression params tables grouping ('Required ('NotNull ty))
427− -> SortExpression params tables grouping
428− AscNullsFirst
429− :: Expression params tables grouping ('Required ('Null ty))
430− -> SortExpression params tables grouping
431− AscNullsLast
432− :: Expression params tables grouping ('Required ('Null ty))
433− -> SortExpression params tables grouping
434− DescNullsFirst
435− :: Expression params tables grouping ('Required ('Null ty))
436− -> SortExpression params tables grouping
437− DescNullsLast
438− :: Expression params tables grouping ('Required ('Null ty))
439− -> SortExpression params tables grouping
440−deriving instance Show (SortExpression params tables grouping)
441−
442−renderSortExpression :: SortExpression params tables grouping -> ByteString
443−renderSortExpression = \case
444− Asc expression -> renderExpression expression <+> "ASC"
445− Desc expression -> renderExpression expression <+> "DESC"
446− AscNullsFirst expression -> renderExpression expression
447− <+> "ASC NULLS FIRST"
448− DescNullsFirst expression -> renderExpression expression
449− <+> "DESC NULLS FIRST"
450− AscNullsLast expression -> renderExpression expression <+> "ASC NULLS LAST"
451− DescNullsLast expression -> renderExpression expression <+> "DESC NULLS LAST"
452−
453−
454−{-----------------------------------------
455−aggregation
456−-----------------------------------------}
457−
458−data By
459− (tables :: [(Symbol,[(Symbol,ColumnType)])])
460− (by :: (Symbol,Symbol)) where
461− By
462− :: (tables ~ '[table ::: columns], HasColumn column columns ty)
463− => Alias column
464− -> By tables '(table, column)
465− By2
466− :: (HasTable table tables columns, HasColumn column columns ty)
467− => (Alias table, Alias column)
468− -> By tables '(table, column)
469−deriving instance Show (By tables by)
470−deriving instance Eq (By tables by)
471−deriving instance Ord (By tables by)
472−
473−renderBy :: By tables tabcolty -> ByteString
474−renderBy = \case
475− By column -> renderAlias column
476− By2 (table, column) -> renderAlias table <> "." <> renderAlias column
477−
478−data GroupByClause tables grouping where
479− NoGroups :: GroupByClause tables 'Ungrouped
480− Group
481− :: SListI bys
482− => NP (By tables) bys
483− -> GroupByClause tables ('Grouped bys)
484−
485−renderGroupByClause :: GroupByClause tables grouping -> ByteString
486−renderGroupByClause = \case
487− NoGroups -> ""
488− Group Nil -> ""
489− Group bys -> " GROUP BY" <+> renderCommaSeparated renderBy bys
490−
491−data HavingClause params tables grouping where
492− NoHaving :: HavingClause params tables 'Ungrouped
493− Having
494− :: [Condition params tables ('Grouped bys)]
495− -> HavingClause params tables ('Grouped bys)
496−deriving instance Show (HavingClause params tables grouping)
497−deriving instance Eq (HavingClause params tables grouping)
498−deriving instance Ord (HavingClause params tables grouping)
499−
500−renderHavingClause :: HavingClause params tables grouping -> ByteString
501−renderHavingClause = \case
502− NoHaving -> ""
503− Having [] -> ""
504− Having conditions ->
505− " HAVING" <+> commaSeparated (renderExpression <$> conditions)
506−
507−class (KnownSymbol table, KnownSymbol column)
508− => GroupedBy table column bys where
509− getGroup1
510− :: (tables ~ '[table ::: columns], HasColumn column columns ty)
511− => Alias column
512− -> Expression params tables ('Grouped bys) ty
513− getGroup1 column = UnsafeExpression $ renderAlias column
514− getGroup2
515− :: (HasTable table tables columns, HasColumn column columns ty)
516− => Alias table
517− -> Alias column
518− -> Expression params tables ('Grouped bys) ty
519− getGroup2 table column = UnsafeExpression $
520− renderAlias table <> "." <> renderAlias column
521−instance {-# OVERLAPPING #-} (KnownSymbol table, KnownSymbol column)
522− => GroupedBy table column ('(table,column) ': bys)
523−instance {-# OVERLAPPABLE #-}
524− ( KnownSymbol table
525− , KnownSymbol column
526− , GroupedBy table column bys
527− ) => GroupedBy table column (tabcol ': bys)
528−
529−instance
530− ( tables ~ '[table ::: columns]
531− , HasColumn column columns ty
532− , GroupedBy table column bys
533− ) => IsLabel column
534− (Expression params tables ('Grouped bys) ty) where
535− fromLabel = getGroup1 (Alias @column)
536−
537−instance
538− ( HasTable table tables columns
539− , HasColumn column columns ty
540− , GroupedBy table column bys
541− ) => IsTableColumn table column
542− (Expression params tables ('Grouped bys) ty) where (!) = getGroup2
543−
544−data AllOrDistinct = All | Distinct
545− deriving (GHC.Generic,Show,Eq,Ord,Data)
546−instance NFData AllOrDistinct
547−
548−renderAllOrDistinct :: AllOrDistinct -> ByteString
549−renderAllOrDistinct = \case
550− All -> "ALL"
551− Distinct -> "DISTINCT"
552−
553−unsafeAggregate
554− :: ByteString
555− -> AllOrDistinct
556− -> Expression params tables 'Ungrouped ('Required xty)
557− -> Expression params tables ('Grouped bys) ('Required yty)
558−unsafeAggregate fun allOrDistinct x = UnsafeExpression $ mconcat
559− [fun, "(", renderAllOrDistinct allOrDistinct <+> renderExpression x, ")"]
560−
561−sum_
562− :: PGNum ty
563− => AllOrDistinct
564− -> Expression params tables 'Ungrouped ('Required (nullity ty))
565− -> Expression params tables ('Grouped bys) ('Required (nullity ty))
566−sum_ = unsafeAggregate "sum"
567−
568−avgInt
569− :: PGIntegral int
570− => AllOrDistinct
571− -> Expression params tables 'Ungrouped ('Required (nullity int))
572− -> Expression params tables ('Grouped bys) ('Required (nullity 'PGnumeric))
573−avgInt = unsafeAggregate "avg"
574−
575−avgFloat
576− :: PGFloating float
577− => AllOrDistinct
578− -> Expression params tables 'Ungrouped ('Required (nullity float))
579− -> Expression params tables ('Grouped bys) ('Required (nullity 'PGfloat8))
580−avgFloat = unsafeAggregate "avg"
581−
582−bitAnd
583− :: PGIntegral int
584− => AllOrDistinct
585− -> Expression params tables 'Ungrouped ('Required (nullity int))
586− -> Expression params tables ('Grouped bys) ('Required (nullity int))
587−bitAnd = unsafeAggregate "bit_and"
588−
589−bitOr
590− :: PGIntegral int
591− => AllOrDistinct
592− -> Expression params tables 'Ungrouped ('Required (nullity int))
593− -> Expression params tables ('Grouped bys) ('Required (nullity int))
594−bitOr = unsafeAggregate "bit_or"
595−
596−boolAnd
597− :: AllOrDistinct
598− -> Expression params tables 'Ungrouped ('Required (nullity 'PGbool))
599− -> Expression params tables ('Grouped bys) ('Required (nullity 'PGbool))
600−boolAnd = unsafeAggregate "bool_and"
601−
602−boolOr
603− :: AllOrDistinct
604− -> Expression params tables 'Ungrouped ('Required (nullity 'PGbool))
605− -> Expression params tables ('Grouped bys) ('Required (nullity 'PGbool))
606−boolOr = unsafeAggregate "bool_or"
607−
608−countStar
609− :: AllOrDistinct
610− -> Expression params table ('Grouped bys) ('Required ('NotNull 'PGint8))
611−countStar allOrDistinct = UnsafeExpression $
612− "count(" <> renderAllOrDistinct allOrDistinct <+> "*)"
613−
614−count
615− :: AllOrDistinct
616− -> Expression params tables 'Ungrouped ('Required ty)
617− -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGint8))
618−count = unsafeAggregate "count"
619−
620−every
621− :: AllOrDistinct
622− -> Expression params tables 'Ungrouped ('Required (nullity 'PGbool))
623− -> Expression params tables ('Grouped bys) ('Required (nullity 'PGbool))
624−every = unsafeAggregate "every"
625−
626−jsonAgg
627− :: AllOrDistinct
628− -> Expression params tables 'Ungrouped ('Required ty)
629− -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjson))
630−jsonAgg = unsafeAggregate "json_agg"
631−
632−jsonbAgg
633− :: AllOrDistinct
634− -> Expression params tables 'Ungrouped ('Required ty)
635− -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjsonb))
636−jsonbAgg = unsafeAggregate "jsonb_agg"
637−
638−jsonObjectAgg
639− :: Expression params tables 'Ungrouped ('Required ('NotNull keyty))
640− -> Expression params tables 'Ungrouped ('Required valty)
641− -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjson))
642−jsonObjectAgg k v = UnsafeExpression $
643− "json_object_agg(" <> renderExpression k
644− <> ", " <> renderExpression v <> ")"
645−
646−jsonbObjectAgg
647− :: AllOrDistinct
648− -> Expression params tables 'Ungrouped ('Required ('NotNull keyty))
649− -> Expression params tables 'Ungrouped ('Required valty)
650− -> Expression params tables ('Grouped bys) ('Required ('NotNull 'PGjsonb))
651−jsonbObjectAgg allOrDistinct k v = UnsafeExpression $
652− "jsonb_object_agg(" <> renderAllOrDistinct allOrDistinct
653− <+> renderExpression k <> ", " <> renderExpression v <> ")"
654−
655−max_
656− :: AllOrDistinct
657− -> Expression params tables 'Ungrouped ('Required (nullity ty))
658− -> Expression params tables ('Grouped bys) ('Required (nullity ty))
659−max_ = unsafeAggregate "max"
660−
661−min_
662− :: AllOrDistinct
663− -> Expression params tables 'Ungrouped ('Required (nullity ty))
664− -> Expression params tables ('Grouped bys) ('Required (nullity ty))
665−min_ = unsafeAggregate "min"
666−
667−{-----------------------------------------
668−tables
669−-----------------------------------------}
670−
671−newtype Table
672− (schema :: [(Symbol,[(Symbol,ColumnType)])])
673− (columns :: [(Symbol,ColumnType)])
674− = UnsafeTable { renderTable :: ByteString }
675− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
676−
677−class KnownSymbol table => HasTable table tables columns
678− | table tables -> columns where
679− getTable :: Alias table -> Table tables columns
680− getTable table = UnsafeTable $ renderAlias table
681−instance {-# OVERLAPPING #-} KnownSymbol table
682− => HasTable table ((table ::: columns) ': tables) columns
683−instance {-# OVERLAPPABLE #-}
684− (KnownSymbol table, HasTable table schema columns)
685− => HasTable table (table' ': schema) columns
686−
687−instance HasTable table schema columns
688− => IsLabel table (Table schema columns) where
689− fromLabel = getTable (Alias @table)
690−
691−data FromClause params schema tables where
692− Table
693− :: Aliased (Table schema) table
694− -> FromClause params schema '[table]
695− Subquery
696− :: Aliased (Query schema params) table
697− -> FromClause params schema '[table]
698− CrossJoin
699− :: FromClause params schema right
700− -> FromClause params schema left
701− -> FromClause params schema (Join left right)
702− InnerJoin
703− :: FromClause params schema right
704− -> Condition params (Join left right) 'Ungrouped
705− -> FromClause params schema left
706− -> FromClause params schema (Join left right)
707− LeftOuterJoin
708− :: FromClause params schema right
709− -> Condition params (Join left right) 'Ungrouped
710− -> FromClause params schema left
711− -> FromClause params schema (Join left (NullifyTables right))
712− RightOuterJoin
713− :: FromClause params schema right
714− -> Condition params (Join left right) 'Ungrouped
715− -> FromClause params schema left
716− -> FromClause params schema (Join (NullifyTables left) right)
717− FullOuterJoin
718− :: FromClause params schema right
719− -> Condition params (Join left right) 'Ungrouped
720− -> FromClause params schema left
721− -> FromClause params schema
722− (Join (NullifyTables left) (NullifyTables right))
723−
724−renderFromClause :: FromClause params schema tables -> ByteString
725−renderFromClause = \case
726− Table table -> renderAliased renderTable table
727− Subquery selection -> renderAliased (parenthesized . renderQuery) selection
728− CrossJoin right left ->
729− renderFromClause left <+> "CROSS JOIN" <+> renderFromClause right
730− InnerJoin right on left -> renderJoin "INNER JOIN" right on left
731− LeftOuterJoin right on left -> renderJoin "LEFT OUTER JOIN" right on left
732− RightOuterJoin right on left -> renderJoin "RIGHT OUTER JOIN" right on left
733− FullOuterJoin right on left -> renderJoin "FULL OUTER JOIN" right on left
734− where
735− renderJoin op right on left =
736− renderFromClause left <+> op <+> renderFromClause right
737− <+> "ON" <+> renderExpression on
738−
739−data TableExpression
740− (params :: [ColumnType])
741− (schema :: [(Symbol,[(Symbol,ColumnType)])])
742− (tables :: [(Symbol,[(Symbol,ColumnType)])])
743− (grouping :: Grouping)
744− = TableExpression
745− { fromClause :: FromClause params schema tables
746− , whereClause :: [Condition params tables 'Ungrouped]
747− , groupByClause :: GroupByClause tables grouping
748− , havingClause :: HavingClause params tables grouping
749− , orderByClause :: [SortExpression params tables grouping]
750− , limitClause :: [Word64]
751− , offsetClause :: [Word64]
752− }
753−
754−renderTableExpression
755− :: TableExpression params schema tables grouping
756− -> ByteString
757−renderTableExpression
758− (TableExpression tables whs' grps' hvs' srts' lims' offs') = mconcat
759− [ "FROM ", renderFromClause tables
760− , renderWheres whs'
761− , renderGroupByClause grps'
762− , renderHavingClause hvs'
763− , renderSorts srts'
764− , renderLimits lims'
765− , renderOffsets offs'
766− ]
767− where
768− renderWheres = \case
769− [] -> ""
770− wh:[] -> " WHERE" <+> renderExpression wh
771− wh:whs -> " WHERE" <+> renderExpression (foldr (&&*) wh whs)
772− renderSorts = \case
773− [] -> ""
774− srts -> " SORT BY"
775− <+> commaSeparated (renderSortExpression <$> srts)
776− renderLimits = \case
777− [] -> ""
778− lims -> " LIMIT" <+> fromString (show (minimum lims))
779− renderOffsets = \case
780− [] -> ""
781− offs -> " OFFSET" <+> fromString (show (sum offs))
782−
783−from
784− :: FromClause params schema tables
785− -> TableExpression params schema tables 'Ungrouped
786−from tables = TableExpression tables [] NoGroups NoHaving [] [] []
787−
788−where_
789− :: Condition params tables 'Ungrouped
790− -> TableExpression params schema tables grouping
791− -> TableExpression params schema tables grouping
792−where_ wh tables = tables {whereClause = wh : whereClause tables}
793−
794−group
795− :: SListI bys
796− => NP (By tables) bys
797− -> TableExpression params schema tables 'Ungrouped
798− -> TableExpression params schema tables ('Grouped bys)
799−group bys tables = TableExpression
800− { fromClause = fromClause tables
801− , whereClause = whereClause tables
802− , groupByClause = Group bys
803− , havingClause = Having []
804− , orderByClause = []
805− , limitClause = limitClause tables
806− , offsetClause = offsetClause tables
807− }
808−
809−having
810− :: Condition params tables ('Grouped bys)
811− -> TableExpression params schema tables ('Grouped bys)
812− -> TableExpression params schema tables ('Grouped bys)
813−having hv tables = tables
814− { havingClause = case havingClause tables of Having hvs -> Having (hv:hvs) }
815−
816−orderBy
817− :: [SortExpression params tables grouping]
818− -> TableExpression params schema tables grouping
819− -> TableExpression params schema tables grouping
820−orderBy srts tables = tables {orderByClause = orderByClause tables ++ srts}
821−
822−limit
823− :: Word64
824− -> TableExpression params schema tables grouping
825− -> TableExpression params schema tables grouping
826−limit lim tables = tables {limitClause = lim : limitClause tables}
827−
828−offset
829− :: Word64
830− -> TableExpression params schema tables grouping
831− -> TableExpression params schema tables grouping
832−offset off tables = tables {offsetClause = off : offsetClause tables}
833−
834−{-----------------------------------------
835−type expressions
836−-----------------------------------------}
837−
838−newtype TypeExpression (ty :: ColumnType)
839− = UnsafeTypeExpression { renderTypeExpression :: ByteString }
840− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
841−
842−bool :: TypeExpression ('Required ('Null 'PGbool))
843−bool = UnsafeTypeExpression "bool"
844−int2 :: TypeExpression ('Required ('Null 'PGint2))
845−int2 = UnsafeTypeExpression "int2"
846−smallint :: TypeExpression ('Required ('Null 'PGint2))
847−smallint = UnsafeTypeExpression "smallint"
848−int4 :: TypeExpression ('Required ('Null 'PGint4))
849−int4 = UnsafeTypeExpression "int4"
850−int :: TypeExpression ('Required ('Null 'PGint4))
851−int = UnsafeTypeExpression "int"
852−integer :: TypeExpression ('Required ('Null 'PGint4))
853−integer = UnsafeTypeExpression "integer"
854−int8 :: TypeExpression ('Required ('Null 'PGint8))
855−int8 = UnsafeTypeExpression "int8"
856−bigint :: TypeExpression ('Required ('Null 'PGint8))
857−bigint = UnsafeTypeExpression "bigint"
858−numeric :: TypeExpression ('Required ('Null 'PGnumeric))
859−numeric = UnsafeTypeExpression "numeric"
860−float4 :: TypeExpression ('Required ('Null 'PGfloat4))
861−float4 = UnsafeTypeExpression "float4"
862−real :: TypeExpression ('Required ('Null 'PGfloat4))
863−real = UnsafeTypeExpression "real"
864−float8 :: TypeExpression ('Required ('Null 'PGfloat8))
865−float8 = UnsafeTypeExpression "float8"
866−doublePrecision :: TypeExpression ('Required ('Null 'PGfloat8))
867−doublePrecision = UnsafeTypeExpression "double precision"
868−serial2 :: TypeExpression ('Optional ('NotNull 'PGint2))
869−serial2 = UnsafeTypeExpression "serial2"
870−smallserial :: TypeExpression ('Optional ('NotNull 'PGint2))
871−smallserial = UnsafeTypeExpression "smallserial"
872−serial4 :: TypeExpression ('Optional ('NotNull 'PGint4))
873−serial4 = UnsafeTypeExpression "serial4"
874−serial :: TypeExpression ('Optional ('NotNull 'PGint4))
875−serial = UnsafeTypeExpression "serial"
876−serial8 :: TypeExpression ('Optional ('NotNull 'PGint8))
877−serial8 = UnsafeTypeExpression "serial8"
878−bigserial :: TypeExpression ('Optional ('NotNull 'PGint8))
879−bigserial = UnsafeTypeExpression "bigserial"
880−text :: TypeExpression ('Required ('Null 'PGtext))
881−text = UnsafeTypeExpression "text"
882−char
883− :: KnownNat n
884− => proxy n
885− -> TypeExpression ('Required ('Null ('PGchar n)))
886−char p = UnsafeTypeExpression $ "char(" <> renderNat p <> ")"
887−character
888− :: KnownNat n
889− => proxy n
890− -> TypeExpression ('Required ('Null ('PGchar n)))
891−character p = UnsafeTypeExpression $ "character(" <> renderNat p <> ")"
892−varchar
893− :: KnownNat n
894− => proxy n
895− -> TypeExpression ('Required ('Null ('PGvarchar n)))
896−varchar p = UnsafeTypeExpression $ "varchar(" <> renderNat p <> ")"
897−characterVarying
898− :: KnownNat n
899− => proxy n
900− -> TypeExpression ('Required ('Null ('PGvarchar n)))
901−characterVarying p = UnsafeTypeExpression $
902− "character varying(" <> renderNat p <> ")"
903−bytea :: TypeExpression ('Required ('Null ('PGbytea)))
904−bytea = UnsafeTypeExpression "bytea"
905−timestamp :: TypeExpression ('Required ('Null ('PGtimestamp)))
906−timestamp = UnsafeTypeExpression "timestamp"
907−timestampWithTimeZone :: TypeExpression ('Required ('Null ('PGtimestamptz)))
908−timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
909−date :: TypeExpression ('Required ('Null ('PGdate)))
910−date = UnsafeTypeExpression "date"
911−time :: TypeExpression ('Required ('Null ('PGtime)))
912−time = UnsafeTypeExpression "time"
913−timeWithTimeZone :: TypeExpression ('Required ('Null ('PGtimetz)))
914−timeWithTimeZone = UnsafeTypeExpression "time with time zone"
915−interval :: TypeExpression ('Required ('Null ('PGinterval)))
916−interval = UnsafeTypeExpression "interval"
917−uuid :: TypeExpression ('Required ('Null ('PGuuid)))
918−uuid = UnsafeTypeExpression "uuid"
919−json :: TypeExpression ('Required ('Null ('PGjson)))
920−json = UnsafeTypeExpression "json"
921−jsonb :: TypeExpression ('Required ('Null ('PGjsonb)))
922−jsonb = UnsafeTypeExpression "jsonb"
923−
924−notNull
925− :: TypeExpression (optionality ('Null ty))
926− -> TypeExpression (optionality ('NotNull ty))
927−notNull ty = UnsafeTypeExpression $ renderTypeExpression ty <+> "NOT NULL"
928−default_
929− :: Expression '[] '[] 'Ungrouped ('Required ty)
930− -> TypeExpression ('Required ty)
931− -> TypeExpression ('Optional ty)
932−default_ x ty = UnsafeTypeExpression $
933− renderTypeExpression ty <+> "DEFAULT" <+> renderExpression x
934−
935−class PGTyped (ty :: PGType) where
936− pgtype :: TypeExpression ('Required ('Null ty))
937−instance PGTyped 'PGbool where pgtype = bool
938−instance PGTyped 'PGint2 where pgtype = int2
939−instance PGTyped 'PGint4 where pgtype = int4
940−instance PGTyped 'PGint8 where pgtype = int8
941−instance PGTyped 'PGnumeric where pgtype = numeric
942−instance PGTyped 'PGfloat4 where pgtype = float4
943−instance PGTyped 'PGfloat8 where pgtype = float8
944−instance PGTyped 'PGtext where pgtype = text
945−instance KnownNat n => PGTyped ('PGchar n) where pgtype = char (Proxy @n)
946−instance KnownNat n => PGTyped ('PGvarchar n) where pgtype = varchar (Proxy @n)
947−instance PGTyped 'PGbytea where pgtype = bytea
948−instance PGTyped 'PGtimestamp where pgtype = timestamp
949−instance PGTyped 'PGtimestamptz where pgtype = timestampWithTimeZone
950−instance PGTyped 'PGdate where pgtype = date
951−instance PGTyped 'PGtime where pgtype = time
952−instance PGTyped 'PGtimetz where pgtype = timeWithTimeZone
953−instance PGTyped 'PGinterval where pgtype = interval
954−instance PGTyped 'PGuuid where pgtype = uuid
955−instance PGTyped 'PGjson where pgtype = json
956−instance PGTyped 'PGjsonb where pgtype = jsonb
957−
958−{-----------------------------------------
959−statements
960−-----------------------------------------}
961−
962−newtype Definition
963− (schema0 :: [(Symbol,[(Symbol,ColumnType)])])
964− (schema1 :: [(Symbol,[(Symbol,ColumnType)])])
965− = UnsafeDefinition { renderDefinition :: ByteString }
966− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
967−
968−instance Category Definition where
969− id = UnsafeDefinition ";"
970− ddl1 . ddl0 = UnsafeDefinition $
971− renderDefinition ddl0 <+> renderDefinition ddl1
972−
973−newtype Manipulation
974− (schema :: [(Symbol,[(Symbol,ColumnType)])])
975− (params :: [ColumnType])
976− (columns :: [(Symbol,ColumnType)])
977− = UnsafeManipulation { renderManipulation :: ByteString }
978− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
979−
980−newtype Query
981− (schema :: [(Symbol,[(Symbol,ColumnType)])])
982− (params :: [ColumnType])
983− (columns :: [(Symbol,ColumnType)])
984− = UnsafeQuery { renderQuery :: ByteString }
985− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
986−
987−queryStatement
988− :: Query schema params columns
989− -> Manipulation schema params columns
990−queryStatement q = UnsafeManipulation $ renderQuery q <> ";"
991−
992−union, unionAll, intersect, intersectAll, except, exceptAll
993− :: Query schema params columns
994− -> Query schema params columns
995− -> Query schema params columns
996−q1 `union` q2 = UnsafeQuery $
997− parenthesized (renderQuery q1)
998− <+> "UNION"
999− <+> parenthesized (renderQuery q2)
1000−q1 `unionAll` q2 = UnsafeQuery $
1001− parenthesized (renderQuery q1)
1002− <+> "UNION" <+> "ALL"
1003− <+> parenthesized (renderQuery q2)
1004−q1 `intersect` q2 = UnsafeQuery $
1005− parenthesized (renderQuery q1)
1006− <+> "INTERSECT"
1007− <+> parenthesized (renderQuery q2)
1008−q1 `intersectAll` q2 = UnsafeQuery $
1009− parenthesized (renderQuery q1)
1010− <+> "INTERSECT" <+> "ALL"
1011− <+> parenthesized (renderQuery q2)
1012−q1 `except` q2 = UnsafeQuery $
1013− parenthesized (renderQuery q1)
1014− <+> "EXCEPT"
1015− <+> parenthesized (renderQuery q2)
1016−q1 `exceptAll` q2 = UnsafeQuery $
1017− parenthesized (renderQuery q1)
1018− <+> "EXCEPT" <+> "ALL"
1019− <+> parenthesized (renderQuery q2)
1020−
1021−{-----------------------------------------
1022−SELECT statements
1023−-----------------------------------------}
1024−
1025−select
1026− :: SListI columns
1027− => NP (Aliased (Expression params tables grouping)) (column ': columns)
1028− -> TableExpression params schema tables grouping
1029− -> Query schema params (column ': columns)
1030−select list tabs = UnsafeQuery $
1031− "SELECT"
1032− <+> renderCommaSeparated (renderAliased renderExpression) list
1033− <+> renderTableExpression tabs
1034−
1035−selectDistinct
1036− :: SListI columns
1037− => NP (Aliased (Expression params tables grouping)) (column ': columns)
1038− -> TableExpression params schema tables grouping
1039− -> Query schema params (column ': columns)
1040−selectDistinct list tabs = UnsafeQuery $
1041− "SELECT DISTINCT"
1042− <+> renderCommaSeparated (renderAliased renderExpression) list
1043− <+> renderTableExpression tabs
1044−
1045−selectStar
1046− :: tables ~ '[table ::: columns]
1047− => TableExpression params schema tables 'Ungrouped
1048− -> Query schema params columns
1049−selectStar tabs = UnsafeQuery $ "SELECT" <+> "*" <+> renderTableExpression tabs
1050−
1051−selectDistinctStar
1052− :: tables ~ '[table ::: columns]
1053− => TableExpression params schema tables 'Ungrouped
1054− -> Query schema params columns
1055−selectDistinctStar tabs = UnsafeQuery $
1056− "SELECT DISTINCT" <+> "*" <+> renderTableExpression tabs
1057−
1058−selectDotStar
1059− :: HasTable table tables columns
1060− => Alias table
1061− -> TableExpression params schema tables 'Ungrouped
1062− -> Query schema params columns
1063−selectDotStar table tables = UnsafeQuery $
1064− "SELECT" <+> renderAlias table <> ".*" <+> renderTableExpression tables
1065−
1066−selectDistinctDotStar
1067− :: HasTable table tables columns
1068− => Alias table
1069− -> TableExpression params schema tables 'Ungrouped
1070− -> Query schema params columns
1071−selectDistinctDotStar table tables = UnsafeQuery $
1072− "SELECT DISTINCT" <+> renderAlias table <> ".*"
1073− <+> renderTableExpression tables
1074−
1075−{-----------------------------------------
1076−CREATE statements
1077−-----------------------------------------}
1078−
1079−createTable
1080− :: (KnownSymbol table, SListI columns)
1081− => Alias table
1082− -> NP (Aliased TypeExpression) (column ': columns)
1083− -> [TableConstraint schema (column ': columns)]
1084− -> Definition schema (Create table (column ': columns) schema)
1085−createTable table columns constraints = UnsafeDefinition $
1086− "CREATE TABLE" <+> renderAlias table
1087− <+> parenthesized
1088− ( renderCommaSeparated renderColumnDef columns
1089− <> renderConstraints constraints )
1090− <> ";"
1091− where
1092− renderColumnDef :: Aliased TypeExpression x -> ByteString
1093− renderColumnDef (ty `As` column) =
1094− renderAlias column <+> renderTypeExpression ty
1095− renderConstraints :: [TableConstraint schema columns] -> ByteString
1096− renderConstraints = \case
1097− [] -> ""
1098− _ -> ", " <> commaSeparated (renderTableConstraint <$> constraints)
1099−
1100−newtype TableConstraint
1101− (schema :: [(Symbol,[(Symbol,ColumnType)])])
1102− (columns :: [(Symbol,ColumnType)])
1103− = UnsafeTableConstraint { renderTableConstraint :: ByteString }
1104− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1105−
1106−check
1107− :: Condition '[] '[table ::: columns] 'Ungrouped
1108− -> TableConstraint schema columns
1109−check condition = UnsafeTableConstraint $
1110− "CHECK" <+> parenthesized (renderExpression condition)
1111−
1112−renderColumn :: Column columns columnty -> ByteString
1113−renderColumn (Column column) = renderAlias column
1114−
1115−unique
1116− :: SListI subcolumns
1117− => NP (Column columns) subcolumns
1118− -> TableConstraint schema columns
1119−unique columns = UnsafeTableConstraint $
1120− "UNIQUE" <+> parenthesized (renderCommaSeparated renderColumn columns)
1121−
1122−primaryKey
1123− :: (SListI subcolumns, AllNotNull subcolumns)
1124− => NP (Column columns) subcolumns
1125− -> TableConstraint schema columns
1126−primaryKey columns = UnsafeTableConstraint $
1127− "PRIMARY KEY" <+> parenthesized (renderCommaSeparated renderColumn columns)
1128−
1129−foreignKey
1130− :: ( HasTable reftable schema refcolumns
1131− , SameTypes subcolumns refsubcolumns
1132− , NotAllNull subcolumns
1133− , SListI subcolumns
1134− , SListI refsubcolumns)
1135− => NP (Column columns) subcolumns
1136− -> Alias reftable
1137− -> NP (Column refcolumns) refsubcolumns
1138− -> OnDelete
1139− -> OnUpdate
1140− -> TableConstraint schema columns
1141−foreignKey columns reftable refcolumns onDelete onUpdate =
1142− UnsafeTableConstraint $
1143− "FOREIGN KEY" <+> parenthesized (renderCommaSeparated renderColumn columns)
1144− <+> "REFERENCES" <+> renderAlias reftable
1145− <+> parenthesized (renderCommaSeparated renderColumn refcolumns)
1146− <+> renderOnDelete onDelete
1147− <+> renderOnUpdate onUpdate
1148−
1149−data OnDelete
1150− = OnDeleteNoAction
1151− | OnDeleteRestrict
1152− | OnDeleteCascade
1153− deriving (GHC.Generic,Show,Eq,Ord,Data)
1154−instance NFData OnDelete
1155−
1156−renderOnDelete :: OnDelete -> ByteString
1157−renderOnDelete = \case
1158− OnDeleteNoAction -> "ON DELETE NO ACTION"
1159− OnDeleteRestrict -> "ON DELETE RESTRICT"
1160− OnDeleteCascade -> "ON DELETE CASCADE"
1161−
1162−data OnUpdate
1163− = OnUpdateNoAction
1164− | OnUpdateRestrict
1165− | OnUpdateCascade
1166− deriving (GHC.Generic,Show,Eq,Ord,Data)
1167−instance NFData OnUpdate
1168−
1169−renderOnUpdate :: OnUpdate -> ByteString
1170−renderOnUpdate = \case
1171− OnUpdateNoAction -> "ON UPDATE NO ACTION"
1172− OnUpdateRestrict -> "ON UPDATE RESTRICT"
1173− OnUpdateCascade -> "ON UPDATE CASCADE"
1174−
1175−{-----------------------------------------
1176−DROP statements
1177−-----------------------------------------}
1178−
1179−dropTable
1180− :: KnownSymbol table
1181− => Alias table
1182− -> Definition schema (Drop table schema)
1183−dropTable table = UnsafeDefinition $ "DROP TABLE" <+> renderAlias table <> ";"
1184−
1185−{-----------------------------------------
1186−ALTER statements
1187−-----------------------------------------}
1188−
1189−alterTable
1190− :: HasTable table schema columns0
1191− => Alias table
1192− -> AlterTable columns0 columns1
1193− -> Definition schema (Alter table schema columns1)
1194−alterTable table alteration = UnsafeDefinition $
1195− "ALTER TABLE"
1196− <+> renderAlias table
1197− <+> renderAlterTable alteration
1198− <> ";"
1199−
1200−alterTableRename
1201− :: (KnownSymbol table0, KnownSymbol table1)
1202− => Alias table0
1203− -> Alias table1
1204− -> Definition schema (Rename table0 table1 schema)
1205−alterTableRename table0 table1 = UnsafeDefinition $
1206− "ALTER TABLE" <+> renderAlias table0
1207− <+> "RENAME TO" <+> renderAlias table1 <> ";"
1208−
1209−newtype AlterTable
1210− (columns0 :: [(Symbol, ColumnType)])
1211− (columns1 :: [(Symbol, ColumnType)]) =
1212− UnsafeAlterTable {renderAlterTable :: ByteString}
1213− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1214−
1215−addColumnDefault
1216− :: KnownSymbol column
1217− => Alias column
1218− -> TypeExpression ('Optional ty)
1219− -> AlterTable columns (Create column ('Optional ty) columns)
1220−addColumnDefault column ty = UnsafeAlterTable $
1221− "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
1222−
1223−addColumnNull
1224− :: KnownSymbol column
1225− => Alias column
1226− -> TypeExpression ('Required ('Null ty))
1227− -> AlterTable columns ((column ::: 'Required ('Null ty)) ': columns)
1228−addColumnNull column ty = UnsafeAlterTable $
1229− "ADD COLUMN" <+> renderAlias column <+> renderTypeExpression ty
1230−
1231−dropColumn
1232− :: KnownSymbol column
1233− => Alias column
1234− -> AlterTable columns (Drop column columns)
1235−dropColumn column = UnsafeAlterTable $
1236− "DROP COLUMN" <+> renderAlias column
1237−
1238−renameColumn
1239− :: (KnownSymbol column0, KnownSymbol column1)
1240− => Alias column0
1241− -> Alias column1
1242− -> AlterTable columns (Rename column0 column1 columns)
1243−renameColumn column0 column1 = UnsafeAlterTable $
1244− "RENAME COLUMN" <+> renderAlias column0 <+> "TO" <+> renderAlias column1
1245−
1246−alterColumn
1247− :: (KnownSymbol column, HasColumn column columns ty0)
1248− => Alias column
1249− -> AlterColumn ty0 ty1
1250− -> AlterTable columns (Alter column columns ty1)
1251−alterColumn column alteration = UnsafeAlterTable $
1252− "ALTER COLUMN" <+> renderAlias column <+> renderAlterColumn alteration
1253−
1254−newtype AlterColumn (ty0 :: ColumnType) (ty1 :: ColumnType) =
1255− UnsafeAlterColumn {renderAlterColumn :: ByteString}
1256− deriving (GHC.Generic,Show,Eq,Ord,Data,NFData)
1257−
1258−setDefault
1259− :: Expression '[] '[] 'Ungrouped ('Required ty)
1260− -> AlterColumn ('Required ty) ('Optional ty)
1261−setDefault expression = UnsafeAlterColumn $
1262− "SET DEFAULT" <+> renderExpression expression
1263−
1264−dropDefault :: AlterColumn ('Optional ty) ('Required ty)
1265−dropDefault = UnsafeAlterColumn $ "DROP DEFAULT"
1266−
1267−setNotNull
1268− :: AlterColumn (optionality ('Null ty)) (optionality ('NotNull ty))
1269−setNotNull = UnsafeAlterColumn $ "SET NOT NULL"
1270−
1271−dropNotNull
1272− :: AlterColumn (optionality ('NotNull ty)) (optionality ('Null ty))
1273−dropNotNull = UnsafeAlterColumn $ "DROP NOT NULL"
1274−
1275−alterType
1276− :: TypeExpression (optionality (nullity ty1))
1277− -> AlterColumn (optionality (nullity ty0)) (optionality (nullity ty1))
1278−alterType ty = UnsafeAlterColumn $ "TYPE " <> renderTypeExpression ty
1279−
1280−{-----------------------------------------
1281−INSERT statements
1282−-----------------------------------------}
1283−
1284−insertInto
1285− :: (SListI columns, SListI results, HasTable table schema columns)
1286− => Alias table
1287− -> ValuesClause schema params columns
1288− -> ConflictClause params columns
1289− -> ReturningClause params columns results
1290− -> Manipulation schema params results
1291−insertInto table insert conflict returning = UnsafeManipulation $
1292− "INSERT" <+> "INTO" <+> renderAlias table
1293− <+> renderValuesClause insert
1294− <> renderConflictClause conflict
1295− <> renderReturningClause returning
1296−
1297−data ValuesClause
1298− (schema :: [(Symbol,[(Symbol,ColumnType)])])
1299− (params :: [ColumnType])
1300− (columns :: [(Symbol,ColumnType)])
1301− = Values
1302− (NP (Aliased (Expression params '[] 'Ungrouped)) columns)
1303− [NP (Aliased (Expression params '[] 'Ungrouped)) columns]
1304− | ValuesQuery (Query schema params columns)
1305−
1306−renderValuesClause
1307− :: SListI columns
1308− => ValuesClause schema params columns
1309− -> ByteString
1310−renderValuesClause = \case
1311− Values row rows ->
1312− parenthesized (renderCommaSeparated renderAliasPart row)
1313− <+> "VALUES"
1314− <+> commaSeparated
1315− (parenthesized . renderCommaSeparated renderValuePart <$> row:rows)
1316− where
1317− renderAliasPart, renderValuePart
1318− :: Aliased (Expression params '[] 'Ungrouped) ty -> ByteString
1319− renderAliasPart (_ `As` name) = renderAlias name
1320− renderValuePart (value `As` _) = renderExpression value
1321− ValuesQuery q -> renderQuery q
1322−
1323−data ReturningClause
1324− (params :: [ColumnType])
1325− (columns :: [(Symbol,ColumnType)])
1326− (results :: [(Symbol,ColumnType)])
1327− where
1328− ReturningStar :: ReturningClause params columns columns
1329− Returning
1330− :: NP
1331− (Aliased (Expression params '[table ::: columns] 'Ungrouped))
1332− results
1333− -> ReturningClause params columns results
1334−
1335−renderReturningClause
1336− :: SListI results
1337− => ReturningClause params columns results
1338− -> ByteString
1339−renderReturningClause = \case
1340− ReturningStar -> " RETURNING *;"
1341− Returning Nil -> ";"
1342− Returning results -> " RETURNING"
1343− <+> renderCommaSeparated (renderAliased renderExpression) results <> ";"
1344−
1345−data ConflictClause params columns where
1346− Conflict :: ConflictClause params columns
1347− OnConflictDoNothing :: ConflictClause params columns
1348− OnConflictDoUpdate
1349− :: NP (Aliased (UpdateExpression params columns)) columns
1350− -> Maybe (Condition params '[table ::: columns] 'Ungrouped)
1351− -> ConflictClause params columns
1352−
1353−renderConflictClause
1354− :: SListI columns
1355− => ConflictClause params columns
1356− -> ByteString
1357−renderConflictClause = \case
1358− Conflict -> ""
1359− OnConflictDoNothing -> " ON CONFLICT DO NOTHING"
1360− OnConflictDoUpdate updates whMaybe
1361− -> " ON CONFLICT DO UPDATE SET"
1362− <+> renderCommaSeparatedMaybe renderUpdateExpression updates
1363− <> case whMaybe of
1364− Nothing -> ""
1365− Just wh -> " WHERE" <+> renderExpression wh
1366−
1367−{-----------------------------------------
1368−UPDATE statements
1369−-----------------------------------------}
1370−
1371−data UpdateExpression params columns ty
1372− = Same
1373− | Set (forall table. Expression params '[table ::: columns] 'Ungrouped ty)
1374−deriving instance Show (UpdateExpression params columns ty)
1375−deriving instance Eq (UpdateExpression params columns ty)
1376−deriving instance Ord (UpdateExpression params columns ty)
1377−
1378−renderUpdateExpression
1379− :: Aliased (UpdateExpression params columns) column
1380− -> Maybe ByteString
1381−renderUpdateExpression = \case
1382− Same `As` _ -> Nothing
1383− Set expression `As` column -> Just $
1384− renderAlias column <+> "=" <+> renderExpression expression
1385−
1386−update
1387− :: (HasTable table schema columns, SListI columns, SListI results)
1388− => Alias table
1389− -> NP (Aliased (UpdateExpression params columns)) columns
1390− -> Condition params '[tab ::: columns] 'Ungrouped
1391− -> ReturningClause params columns results
1392− -> Manipulation schema params results
1393−update table columns wh returning = UnsafeManipulation $
1394− "UPDATE"
1395− <+> renderAlias table
1396− <+> "SET"
1397− <+> renderCommaSeparatedMaybe renderUpdateExpression columns
1398− <+> "WHERE" <+> renderExpression wh
1399− <> renderReturningClause returning
1400−
1401−{-----------------------------------------
1402−DELETE statements
1403−-----------------------------------------}
1404−
1405−deleteFrom
1406− :: HasTable table schema columns
1407− => Alias table
1408− -> Condition params '[table ::: columns] 'Ungrouped
1409− -> Manipulation schema params '[]
1410−deleteFrom table wh = UnsafeManipulation $
1411− "DELETE FROM" <+> renderAlias table
1412− <+> "WHERE" <+> renderExpression wh <> ";"
1413−
1414−{-----------------------------------------
1415−render helper functions
1416−-----------------------------------------}
1417−
1418−parenthesized :: ByteString -> ByteString
1419−parenthesized str = "(" <> str <> ")"
1420−
1421−(<+>) :: ByteString -> ByteString -> ByteString
1422−str1 <+> str2 = str1 <> " " <> str2
1423−
1424−commaSeparated :: [ByteString] -> ByteString
1425−commaSeparated = ByteString.intercalate ", "
1426−
1427−renderCommaSeparated
1428− :: SListI xs
1429− => (forall x. expression x -> ByteString)
1430− -> NP expression xs -> ByteString
1431−renderCommaSeparated render
1432− = commaSeparated
1433− . hcollapse
1434− . hmap (K . render)
1435−
1436−renderCommaSeparatedMaybe
1437− :: SListI xs
1438− => (forall x. expression x -> Maybe ByteString)
1439− -> NP expression xs -> ByteString
1440−renderCommaSeparatedMaybe render
1441− = commaSeparated
1442− . catMaybes
1443− . hcollapse
1444− . hmap (K . render)
1445−
1446−renderNat :: KnownNat n => proxy n -> ByteString
1447−renderNat (_ :: proxy n) = fromString (show (natVal' (proxy# :: Proxy# n)))
squeal-postgresql/test/DocTest.hsnew file+12
@@ -0,0 +1,12 @@
1+module Main (main) where
2+
3+import Test.DocTest
4+
5+main :: IO ()
6+main = doctest
7+ [ "-isrc"
8+ , "src/Squeal/PostgreSQL/Binary.hs"
9+ , "src/Squeal/PostgreSQL/Definition.hs"
10+ , "src/Squeal/PostgreSQL/Manipulation.hs"
11+ , "src/Squeal/PostgreSQL/Query.hs"
12+ ]
squeal-postgresql/test/Squeal/PostgreSQL/DefinitionSpec.hsnew file+124
@@ -0,0 +1,124 @@
1+{-# LANGUAGE
2+ DataKinds
3+ , MagicHash
4+ , OverloadedLabels
5+ , OverloadedStrings
6+ , TypeApplications
7+ , TypeOperators
8+#-}
9+
10+module Squeal.PostgreSQL.DefinitionSpec where
11+
12+import Control.Category ((>>>))
13+import Data.Function
14+import Generics.SOP hiding (from)
15+import Test.Hspec
16+
17+import Squeal.PostgreSQL
18+
19+spec :: Spec
20+spec = do
21+ let
22+ definition `definitionRenders` str =
23+ renderDefinition definition `shouldBe` str
24+ it "should render CREATE TABLE statements" $ do
25+ createTable #table1
26+ ((int4 & notNull) `As` #col1 :* (int4 & notNull) `As` #col2 :* Nil)
27+ [primaryKey (Column #col1 :* Column #col2 :* Nil)]
28+ `definitionRenders`
29+ "CREATE TABLE table1\
30+ \ (col1 int4 NOT NULL, col2 int4 NOT NULL,\
31+ \ PRIMARY KEY (col1, col2));"
32+ createTable #table2
33+ ( serial `As` #col1 :*
34+ text `As` #col2 :*
35+ (int8 & notNull & default_ 8) `As` #col3 :* Nil )
36+ [check (#col3 .> 0)]
37+ `definitionRenders`
38+ "CREATE TABLE table2\
39+ \ (col1 serial,\
40+ \ col2 text,\
41+ \ col3 int8 NOT NULL DEFAULT 8,\
42+ \ CHECK ((col3 > 0)));"
43+ let
44+ statement :: Definition '[]
45+ '[ "users" :::
46+ '[ "id" ::: 'Optional ('NotNull 'PGint4)
47+ , "username" ::: 'Required ('NotNull 'PGtext)
48+ ]
49+ , "emails" :::
50+ '[ "id" ::: 'Optional ('NotNull 'PGint4)
51+ , "userid" ::: 'Required ('NotNull 'PGint4)
52+ , "email" ::: 'Required ('NotNull 'PGtext)
53+ ]
54+ ]
55+ statement =
56+ createTable #users
57+ (serial `As` #id :* (text & notNull) `As` #username :* Nil)
58+ [primaryKey (Column #id :* Nil)]
59+ >>>
60+ createTable #emails
61+ ( serial `As` #id :*
62+ (integer & notNull) `As` #userid :*
63+ (text & notNull) `As` #email :* Nil )
64+ [ primaryKey (Column #id :* Nil)
65+ , foreignKey (Column #userid :* Nil) #users (Column #id :* Nil)
66+ OnDeleteCascade OnUpdateRestrict
67+ , unique (Column #email :* Nil)
68+ , check (#email ./= "")
69+ ]
70+ statement `definitionRenders`
71+ "CREATE TABLE users\
72+ \ (id serial, username text NOT NULL,\
73+ \ PRIMARY KEY (id));\
74+ \ \
75+ \CREATE TABLE emails\
76+ \ (id serial,\
77+ \ userid integer NOT NULL,\
78+ \ email text NOT NULL,\
79+ \ PRIMARY KEY (id),\
80+ \ FOREIGN KEY (userid) REFERENCES users (id)\
81+ \ ON DELETE CASCADE ON UPDATE RESTRICT,\
82+ \ UNIQUE (email),\
83+ \ CHECK ((email <> E'')));"
84+ it "should render DROP TABLE statements" $ do
85+ let
86+ statement :: Definition Tables '[]
87+ statement = dropTable #table1
88+ statement `definitionRenders` "DROP TABLE table1;"
89+
90+type Columns =
91+ '[ "col1" ::: 'Required ('NotNull 'PGint4)
92+ , "col2" ::: 'Required ('NotNull 'PGint4)
93+ ]
94+type Tables = '[ "table1" ::: Columns ]
95+type SumAndCol1 =
96+ '[ "sum" ::: 'Required ('NotNull 'PGint4)
97+ , "col1" ::: 'Required ('NotNull 'PGint4)
98+ ]
99+type StudentsColumns = '["name" ::: 'Required ('NotNull 'PGtext)]
100+type StudentsTable = '["students" ::: StudentsColumns]
101+type OrderColumns =
102+ '[ "orderID" ::: 'Required ('NotNull 'PGint4)
103+ , "orderVal" ::: 'Required ('NotNull 'PGtext)
104+ , "customerID" ::: 'Required ('NotNull 'PGint4)
105+ , "shipperID" ::: 'Required ('NotNull 'PGint4)
106+ ]
107+type CustomerColumns =
108+ '[ "customerID" ::: 'Required ('NotNull 'PGint4)
109+ , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
110+ ]
111+type ShipperColumns =
112+ '[ "shipperID" ::: 'Required ('NotNull 'PGint4)
113+ , "shipperVal" ::: 'Required ('NotNull 'PGbool)
114+ ]
115+type JoinTables =
116+ '[ "orders" ::: OrderColumns
117+ , "customers" ::: CustomerColumns
118+ , "shippers" ::: ShipperColumns
119+ ]
120+type ValueColumns =
121+ '[ "orderVal" ::: 'Required ('NotNull 'PGtext)
122+ , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
123+ , "shipperVal" ::: 'Required ('NotNull 'PGbool)
124+ ]
squeal-postgresql/test/Squeal/PostgreSQL/ManipulationSpec.hsnew file+128
@@ -0,0 +1,128 @@
1+{-# LANGUAGE
2+ DataKinds
3+ , MagicHash
4+ , OverloadedLabels
5+ , OverloadedStrings
6+ , TypeApplications
7+ , TypeOperators
8+#-}
9+
10+module Squeal.PostgreSQL.ManipulationSpec where
11+
12+import Generics.SOP hiding (from)
13+import Test.Hspec
14+
15+import Squeal.PostgreSQL
16+
17+spec :: Spec
18+spec = do
19+ let
20+ manipulation `manipulationRenders` str =
21+ renderManipulation manipulation `shouldBe` str
22+ it "correctly renders returning INSERTs" $ do
23+ let
24+ statement :: Manipulation Tables '[] SumAndCol1
25+ statement =
26+ insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
27+ Conflict
28+ (Returning $ (#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
29+ statement `manipulationRenders`
30+ "INSERT INTO table1 (col1, col2) VALUES (2, 4)\
31+ \ RETURNING (col1 + col2) AS sum, col1 AS col1;"
32+ it "correctly renders simple UPDATEs" $ do
33+ let
34+ statement :: Manipulation Tables '[] '[]
35+ statement =
36+ update #table1 (Set 2 `As` #col1 :* Same `As` #col2 :* Nil)
37+ (#col1 ./= #col2) (Returning Nil)
38+ statement `manipulationRenders`
39+ "UPDATE table1 SET col1 = 2\
40+ \ WHERE (col1 <> col2);"
41+ it "correctly renders returning UPDATEs" $ do
42+ let
43+ statement :: Manipulation Tables '[] SumAndCol1
44+ statement =
45+ update #table1 (Set 2 `As` #col1 :* Same `As` #col2 :* Nil)
46+ (#col1 ./= #col2)
47+ (Returning $ (#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
48+ statement `manipulationRenders`
49+ "UPDATE table1 SET col1 = 2\
50+ \ WHERE (col1 <> col2)\
51+ \ RETURNING (col1 + col2) AS sum, col1 AS col1;"
52+ it "correctly renders upsert INSERTs" $ do
53+ let
54+ statement :: Manipulation Tables '[] '[]
55+ statement =
56+ insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
57+ (OnConflictDoUpdate
58+ (Set 2 `As` #col1 :* Same `As` #col2 :* Nil) Nothing)
59+ (Returning Nil)
60+ statement `manipulationRenders`
61+ "INSERT INTO table1 (col1, col2) VALUES (2, 4)\
62+ \ ON CONFLICT DO UPDATE\
63+ \ SET col1 = 2;"
64+ it "correctly renders returning upsert INSERTs" $ do
65+ let
66+ statement :: Manipulation Tables '[] SumAndCol1
67+ statement =
68+ insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
69+ (OnConflictDoUpdate
70+ (Set 2 `As` #col1 :* Same `As` #col2 :* Nil)
71+ (Just (#col1 ./= #col2)))
72+ (Returning $ (#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
73+ statement `manipulationRenders`
74+ "INSERT INTO table1 (col1, col2) VALUES (2, 4)\
75+ \ ON CONFLICT DO UPDATE\
76+ \ SET col1 = 2\
77+ \ WHERE (col1 <> col2)\
78+ \ RETURNING (col1 + col2) AS sum, col1 AS col1;"
79+ it "correctly renders DELETEs" $ do
80+ let
81+ statement :: Manipulation Tables '[] '[]
82+ statement = deleteFrom #table1 (#col1 .== #col2)
83+ statement `manipulationRenders`
84+ "DELETE FROM table1 WHERE (col1 = col2);"
85+ it "should be safe against SQL injection in literal text" $ do
86+ let
87+ statement :: Manipulation StudentsTable '[] '[]
88+ statement = insertInto #students
89+ (Values ("Robert'); DROP TABLE students;" `As` #name :* Nil) [])
90+ Conflict (Returning Nil)
91+ statement `manipulationRenders`
92+ "INSERT INTO students (name) VALUES (E'Robert''); DROP TABLE students;');"
93+
94+type Columns =
95+ '[ "col1" ::: 'Required ('NotNull 'PGint4)
96+ , "col2" ::: 'Required ('NotNull 'PGint4)
97+ ]
98+type Tables = '[ "table1" ::: Columns ]
99+type SumAndCol1 =
100+ '[ "sum" ::: 'Required ('NotNull 'PGint4)
101+ , "col1" ::: 'Required ('NotNull 'PGint4)
102+ ]
103+type StudentsColumns = '["name" ::: 'Required ('NotNull 'PGtext)]
104+type StudentsTable = '["students" ::: StudentsColumns]
105+type OrderColumns =
106+ '[ "orderID" ::: 'Required ('NotNull 'PGint4)
107+ , "orderVal" ::: 'Required ('NotNull 'PGtext)
108+ , "customerID" ::: 'Required ('NotNull 'PGint4)
109+ , "shipperID" ::: 'Required ('NotNull 'PGint4)
110+ ]
111+type CustomerColumns =
112+ '[ "customerID" ::: 'Required ('NotNull 'PGint4)
113+ , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
114+ ]
115+type ShipperColumns =
116+ '[ "shipperID" ::: 'Required ('NotNull 'PGint4)
117+ , "shipperVal" ::: 'Required ('NotNull 'PGbool)
118+ ]
119+type JoinTables =
120+ '[ "orders" ::: OrderColumns
121+ , "customers" ::: CustomerColumns
122+ , "shippers" ::: ShipperColumns
123+ ]
124+type ValueColumns =
125+ '[ "orderVal" ::: 'Required ('NotNull 'PGtext)
126+ , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
127+ , "shipperVal" ::: 'Required ('NotNull 'PGbool)
128+ ]
squeal-postgresql/test/Squeal/PostgreSQL/QuerySpec.hsnew file+181
@@ -0,0 +1,181 @@
1+{-# LANGUAGE
2+ DataKinds
3+ , MagicHash
4+ , OverloadedLabels
5+ , OverloadedStrings
6+ , TypeApplications
7+ , TypeOperators
8+#-}
9+
10+module Squeal.PostgreSQL.QuerySpec where
11+
12+import Data.Function
13+import Generics.SOP hiding (from)
14+import Test.Hspec
15+
16+import Squeal.PostgreSQL
17+
18+spec :: Spec
19+spec = do
20+ let
21+ qry `queryRenders` str =
22+ renderManipulation (queryStatement qry) `shouldBe` str
23+ it "correctly renders a simple SELECT query" $ do
24+ let
25+ statement :: Query Tables '[] SumAndCol1
26+ statement =
27+ select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
28+ (from (Table (#table1 `As` #table1)) & where_ (#col1 .> #col2))
29+ statement `queryRenders`
30+ "SELECT (col1 + col2) AS sum, col1 AS col1\
31+ \ FROM table1 AS table1 WHERE (col1 > col2);"
32+ it "combines WHEREs using AND" $ do
33+ let
34+ statement :: Query Tables '[] SumAndCol1
35+ statement =
36+ select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
37+ (from (Table (#table1 `As` #table1)) & where_ true & where_ false)
38+ statement `queryRenders`
39+ "SELECT (col1 + col2) AS sum, col1 AS col1\
40+ \ FROM table1 AS table1 WHERE (TRUE AND FALSE);"
41+ it "performs sub SELECTs" $ do
42+ let
43+ statement :: Query Tables '[] SumAndCol1
44+ statement =
45+ selectStar (from (Subquery
46+ (select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
47+ (from (Table (#table1 `As` #table1))) `As` #sub)))
48+ statement `queryRenders`
49+ "SELECT * FROM\
50+ \ (SELECT (col1 + col2) AS sum, col1 AS col1\
51+ \ FROM table1 AS table1) AS sub;"
52+ it "does LIMIT clauses" $ do
53+ let
54+ statement :: Query Tables '[] Columns
55+ statement = selectStar (from (Table (#table1 `As` #table1)) & limit 1)
56+ statement `queryRenders` "SELECT * FROM table1 AS table1 LIMIT 1;"
57+ it "should use the minimum of given LIMITs" $ do
58+ let
59+ statement :: Query Tables '[] Columns
60+ statement =
61+ selectStar (from (Table (#table1 `As` #table1)) & limit 1 & limit 2)
62+ statement `queryRenders` "SELECT * FROM table1 AS table1 LIMIT 1;"
63+ it "should render parameters using $ signs" $ do
64+ let
65+ statement :: Query Tables '[ 'Required ('NotNull 'PGbool)] Columns
66+ statement = selectStar
67+ (from (Table (#table1 `As` #table1)) & where_ (param @1))
68+ statement `queryRenders`
69+ "SELECT * FROM table1 AS table1 WHERE ($1 :: bool);"
70+ it "does OFFSET clauses" $ do
71+ let
72+ statement :: Query Tables '[] Columns
73+ statement =
74+ selectStar (from (Table (#table1 `As` #table1)) & offset 1)
75+ statement `queryRenders` "SELECT * FROM table1 AS table1 OFFSET 1;"
76+ it "should use the sum of given OFFSETs" $ do
77+ let
78+ statement :: Query Tables '[] Columns
79+ statement = selectStar
80+ (from (Table (#table1 `As` #table1)) & offset 1 & offset 2)
81+ statement `queryRenders` "SELECT * FROM table1 AS table1 OFFSET 3;"
82+ it "should render GROUP BY and HAVING clauses" $ do
83+ let
84+ statement :: Query Tables '[] SumAndCol1
85+ statement =
86+ select (sum_ #col2 `As` #sum :* #col1 `As` #col1 :* Nil)
87+ ( from (Table (#table1 `As` #table1))
88+ & group (By #col1 :* Nil)
89+ & having (#col1 + sum_ #col2 .> 1) )
90+ statement `queryRenders`
91+ "SELECT sum(col2) AS sum, col1 AS col1\
92+ \ FROM table1 AS table1\
93+ \ GROUP BY col1\
94+ \ HAVING ((col1 + sum(col2)) > 1);"
95+ describe "JOINs" $ do
96+ it "should render CROSS JOINs" $ do
97+ let
98+ statement :: Query JoinTables '[] ValueColumns
99+ statement = select
100+ ( #orders ! #orderVal `As` #orderVal :*
101+ #customers ! #customerVal `As` #customerVal :*
102+ #shippers ! #shipperVal `As` #shipperVal :* Nil )
103+ ( from (Table (#orders `As` #orders)
104+ & CrossJoin (Table (#customers `As` #customers))
105+ & CrossJoin (Table (#shippers `As` #shippers))) )
106+ statement `queryRenders`
107+ "SELECT\
108+ \ orders.orderVal AS orderVal,\
109+ \ customers.customerVal AS customerVal,\
110+ \ shippers.shipperVal AS shipperVal\
111+ \ FROM orders AS orders\
112+ \ CROSS JOIN customers AS customers\
113+ \ CROSS JOIN shippers AS shippers;"
114+ it "should render INNER JOINs" $ do
115+ let
116+ statement :: Query JoinTables '[] ValueColumns
117+ statement = select
118+ ( (#orders ! #orderVal) `As` #orderVal
119+ :* (#customers ! #customerVal) `As` #customerVal
120+ :* (#shippers ! #shipperVal) `As` #shipperVal :* Nil)
121+ ( from (Table (#orders `As` #orders)
122+ & InnerJoin (Table (#customers `As` #customers))
123+ ((#orders ! #customerID) .== (#customers ! #customerID))
124+ & InnerJoin (Table (#shippers `As` #shippers))
125+ ((#orders ! #shipperID) .== (#shippers ! #shipperID))))
126+ statement `queryRenders`
127+ "SELECT\
128+ \ orders.orderVal AS orderVal,\
129+ \ customers.customerVal AS customerVal,\
130+ \ shippers.shipperVal AS shipperVal\
131+ \ FROM orders AS orders\
132+ \ INNER JOIN customers AS customers\
133+ \ ON (orders.customerID = customers.customerID)\
134+ \ INNER JOIN shippers AS shippers\
135+ \ ON (orders.shipperID = shippers.shipperID);"
136+ it "should render self JOINs" $ do
137+ let
138+ statement :: Query JoinTables '[] OrderColumns
139+ statement = selectDotStar #orders1
140+ (from (Table (#orders `As` #orders1)
141+ & CrossJoin (Table (#orders `As` #orders2))))
142+ statement `queryRenders`
143+ "SELECT orders1.*\
144+ \ FROM orders AS orders1\
145+ \ CROSS JOIN orders AS orders2;"
146+
147+type Columns =
148+ '[ "col1" ::: 'Required ('NotNull 'PGint4)
149+ , "col2" ::: 'Required ('NotNull 'PGint4)
150+ ]
151+type Tables = '[ "table1" ::: Columns ]
152+type SumAndCol1 =
153+ '[ "sum" ::: 'Required ('NotNull 'PGint4)
154+ , "col1" ::: 'Required ('NotNull 'PGint4)
155+ ]
156+type StudentsColumns = '["name" ::: 'Required ('NotNull 'PGtext)]
157+type StudentsTable = '["students" ::: StudentsColumns]
158+type OrderColumns =
159+ '[ "orderID" ::: 'Required ('NotNull 'PGint4)
160+ , "orderVal" ::: 'Required ('NotNull 'PGtext)
161+ , "customerID" ::: 'Required ('NotNull 'PGint4)
162+ , "shipperID" ::: 'Required ('NotNull 'PGint4)
163+ ]
164+type CustomerColumns =
165+ '[ "customerID" ::: 'Required ('NotNull 'PGint4)
166+ , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
167+ ]
168+type ShipperColumns =
169+ '[ "shipperID" ::: 'Required ('NotNull 'PGint4)
170+ , "shipperVal" ::: 'Required ('NotNull 'PGbool)
171+ ]
172+type JoinTables =
173+ '[ "orders" ::: OrderColumns
174+ , "customers" ::: CustomerColumns
175+ , "shippers" ::: ShipperColumns
176+ ]
177+type ValueColumns =
178+ '[ "orderVal" ::: 'Required ('NotNull 'PGtext)
179+ , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
180+ , "shipperVal" ::: 'Required ('NotNull 'PGbool)
181+ ]
squeal-postgresql/test/Squeal/PostgreSQL/StatementSpec.hsdeleted−330
@@ -1,330 +0,0 @@
1−{-# LANGUAGE
2− DataKinds
3− , MagicHash
4− , OverloadedLabels
5− , OverloadedStrings
6− , TypeApplications
7− , TypeOperators
8−#-}
9−
10−module Squeal.PostgreSQL.StatementSpec where
11−
12−import Control.Category ((>>>))
13−import Data.Function
14−import Generics.SOP hiding (from)
15−import Test.Hspec
16−
17−import Squeal.PostgreSQL.Statement
18−import Squeal.PostgreSQL.Schema
19−
20−spec :: Spec
21−spec = do
22− let
23− qry `queryRenders` str = queryStatement qry `manipulationRenders` str
24− definition `definitionRenders` str =
25− renderDefinition definition `shouldBe` str
26− manipulation `manipulationRenders` str =
27− renderManipulation manipulation `shouldBe` str
28− it "correctly renders a simple SELECT query" $ do
29− let
30− statement :: Query Tables '[] SumAndCol1
31− statement =
32− select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
33− (from (Table (#table1 `As` #table1)) & where_ (#col1 >* #col2))
34− statement `queryRenders`
35− "SELECT (col1 + col2) AS sum, col1 AS col1\
36− \ FROM table1 AS table1 WHERE (col1 > col2);"
37− it "combines WHEREs using AND" $ do
38− let
39− statement :: Query Tables '[] SumAndCol1
40− statement =
41− select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
42− (from (Table (#table1 `As` #table1)) & where_ true & where_ false)
43− statement `queryRenders`
44− "SELECT (col1 + col2) AS sum, col1 AS col1\
45− \ FROM table1 AS table1 WHERE (TRUE AND FALSE);"
46− it "performs sub SELECTs" $ do
47− let
48− statement :: Query Tables '[] SumAndCol1
49− statement =
50− selectStar (from (Subquery
51− (select ((#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
52− (from (Table (#table1 `As` #table1))) `As` #sub)))
53− statement `queryRenders`
54− "SELECT * FROM\
55− \ (SELECT (col1 + col2) AS sum, col1 AS col1\
56− \ FROM table1 AS table1) AS sub;"
57− it "does LIMIT clauses" $ do
58− let
59− statement :: Query Tables '[] Columns
60− statement = selectStar (from (Table (#table1 `As` #table1)) & limit 1)
61− statement `queryRenders` "SELECT * FROM table1 AS table1 LIMIT 1;"
62− it "should use the minimum of given LIMITs" $ do
63− let
64− statement :: Query Tables '[] Columns
65− statement =
66− selectStar (from (Table (#table1 `As` #table1)) & limit 1 & limit 2)
67− statement `queryRenders` "SELECT * FROM table1 AS table1 LIMIT 1;"
68− it "should render parameters using $ signs" $ do
69− let
70− statement :: Query Tables '[ 'Required ('NotNull 'PGbool)] Columns
71− statement = selectStar
72− (from (Table (#table1 `As` #table1)) & where_ (param (Proxy @1)))
73− statement `queryRenders`
74− "SELECT * FROM table1 AS table1 WHERE ($1 :: bool);"
75− it "does OFFSET clauses" $ do
76− let
77− statement :: Query Tables '[] Columns
78− statement =
79− selectStar (from (Table (#table1 `As` #table1)) & offset 1)
80− statement `queryRenders` "SELECT * FROM table1 AS table1 OFFSET 1;"
81− it "should use the sum of given OFFSETs" $ do
82− let
83− statement :: Query Tables '[] Columns
84− statement = selectStar
85− (from (Table (#table1 `As` #table1)) & offset 1 & offset 2)
86− statement `queryRenders` "SELECT * FROM table1 AS table1 OFFSET 3;"
87− it "should render GROUP BY and HAVING clauses" $ do
88− let
89− statement :: Query Tables '[] SumAndCol1
90− statement =
91− select (sum_ All #col2 `As` #sum :* #col1 `As` #col1 :* Nil)
92− ( from (Table (#table1 `As` #table1))
93− & group (By #col1 :* Nil)
94− & having (#col1 + sum_ All #col2 >* 1) )
95− statement `queryRenders`
96− "SELECT sum(ALL col2) AS sum, col1 AS col1\
97− \ FROM table1 AS table1\
98− \ GROUP BY col1\
99− \ HAVING ((col1 + sum(ALL col2)) > 1);"
100− it "correctly renders simple INSERTs" $ do
101− let
102− statement :: Manipulation Tables '[] '[]
103− statement =
104− insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
105− Conflict (Returning Nil)
106− statement `manipulationRenders`
107− "INSERT INTO table1 (col1, col2) VALUES (2, 4);"
108− it "correctly renders returning INSERTs" $ do
109− let
110− statement :: Manipulation Tables '[] SumAndCol1
111− statement =
112− insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
113− Conflict
114− (Returning $ (#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
115− statement `manipulationRenders`
116− "INSERT INTO table1 (col1, col2) VALUES (2, 4)\
117− \ RETURNING (col1 + col2) AS sum, col1 AS col1;"
118− it "correctly renders simple UPDATEs" $ do
119− let
120− statement :: Manipulation Tables '[] '[]
121− statement =
122− update #table1 (Set 2 `As` #col1 :* Same `As` #col2 :* Nil)
123− (#col1 /=* #col2) (Returning Nil)
124− statement `manipulationRenders`
125− "UPDATE table1 SET col1 = 2\
126− \ WHERE (col1 <> col2);"
127− it "correctly renders returning UPDATEs" $ do
128− let
129− statement :: Manipulation Tables '[] SumAndCol1
130− statement =
131− update #table1 (Set 2 `As` #col1 :* Same `As` #col2 :* Nil)
132− (#col1 /=* #col2)
133− (Returning $ (#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
134− statement `manipulationRenders`
135− "UPDATE table1 SET col1 = 2\
136− \ WHERE (col1 <> col2)\
137− \ RETURNING (col1 + col2) AS sum, col1 AS col1;"
138− it "correctly renders upsert INSERTs" $ do
139− let
140− statement :: Manipulation Tables '[] '[]
141− statement =
142− insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
143− (OnConflictDoUpdate
144− (Set 2 `As` #col1 :* Same `As` #col2 :* Nil) Nothing)
145− (Returning Nil)
146− statement `manipulationRenders`
147− "INSERT INTO table1 (col1, col2) VALUES (2, 4)\
148− \ ON CONFLICT DO UPDATE\
149− \ SET col1 = 2;"
150− it "correctly renders returning upsert INSERTs" $ do
151− let
152− statement :: Manipulation Tables '[] SumAndCol1
153− statement =
154− insertInto #table1 (Values (2 `As` #col1 :* 4 `As` #col2 :* Nil) [])
155− (OnConflictDoUpdate
156− (Set 2 `As` #col1 :* Same `As` #col2 :* Nil)
157− (Just (#col1 /=* #col2)))
158− (Returning $ (#col1 + #col2) `As` #sum :* #col1 `As` #col1 :* Nil)
159− statement `manipulationRenders`
160− "INSERT INTO table1 (col1, col2) VALUES (2, 4)\
161− \ ON CONFLICT DO UPDATE\
162− \ SET col1 = 2\
163− \ WHERE (col1 <> col2)\
164− \ RETURNING (col1 + col2) AS sum, col1 AS col1;"
165− it "correctly renders DELETEs" $ do
166− let
167− statement :: Manipulation Tables '[] '[]
168− statement = deleteFrom #table1 (#col1 ==* #col2)
169− statement `manipulationRenders`
170− "DELETE FROM table1 WHERE (col1 = col2);"
171− it "should be safe against SQL injection in literal text" $ do
172− let
173− statement :: Manipulation StudentsTable '[] '[]
174− statement = insertInto #students
175− (Values ("Robert'); DROP TABLE students;" `As` #name :* Nil) [])
176− Conflict (Returning Nil)
177− statement `manipulationRenders`
178− "INSERT INTO students (name) VALUES (E'Robert''); DROP TABLE students;');"
179− describe "JOINs" $ do
180− it "should render CROSS JOINs" $ do
181− let
182− statement :: Query JoinTables '[] ValueColumns
183− statement = select
184− ( #orders ! #orderVal `As` #orderVal :*
185− #customers ! #customerVal `As` #customerVal :*
186− #shippers ! #shipperVal `As` #shipperVal :* Nil )
187− ( from (Table (#orders `As` #orders)
188− & CrossJoin (Table (#customers `As` #customers))
189− & CrossJoin (Table (#shippers `As` #shippers))) )
190− statement `queryRenders`
191− "SELECT\
192− \ orders.orderVal AS orderVal,\
193− \ customers.customerVal AS customerVal,\
194− \ shippers.shipperVal AS shipperVal\
195− \ FROM orders AS orders\
196− \ CROSS JOIN customers AS customers\
197− \ CROSS JOIN shippers AS shippers;"
198− it "should render INNER JOINs" $ do
199− let
200− statement :: Query JoinTables '[] ValueColumns
201− statement = select
202− ( (#orders ! #orderVal) `As` #orderVal
203− :* (#customers ! #customerVal) `As` #customerVal
204− :* (#shippers ! #shipperVal) `As` #shipperVal :* Nil)
205− ( from (Table (#orders `As` #orders)
206− & InnerJoin (Table (#customers `As` #customers))
207− ((#orders ! #customerID) ==* (#customers ! #customerID))
208− & InnerJoin (Table (#shippers `As` #shippers))
209− ((#orders ! #shipperID) ==* (#shippers ! #shipperID))))
210− statement `queryRenders`
211− "SELECT\
212− \ orders.orderVal AS orderVal,\
213− \ customers.customerVal AS customerVal,\
214− \ shippers.shipperVal AS shipperVal\
215− \ FROM orders AS orders\
216− \ INNER JOIN customers AS customers\
217− \ ON (orders.customerID = customers.customerID)\
218− \ INNER JOIN shippers AS shippers\
219− \ ON (orders.shipperID = shippers.shipperID);"
220− it "should render self JOINs" $ do
221− let
222− statement :: Query JoinTables '[] OrderColumns
223− statement = selectDotStar #orders1
224− (from (Table (#orders `As` #orders1)
225− & CrossJoin (Table (#orders `As` #orders2))))
226− statement `queryRenders`
227− "SELECT orders1.*\
228− \ FROM orders AS orders1\
229− \ CROSS JOIN orders AS orders2;"
230− it "should render CREATE TABLE statements" $ do
231− createTable #table1
232− ((int4 & notNull) `As` #col1 :* (int4 & notNull) `As` #col2 :* Nil)
233− [primaryKey (Column #col1 :* Column #col2 :* Nil)]
234− `definitionRenders`
235− "CREATE TABLE table1\
236− \ (col1 int4 NOT NULL, col2 int4 NOT NULL,\
237− \ PRIMARY KEY (col1, col2));"
238− createTable #table2
239− ( serial `As` #col1 :*
240− text `As` #col2 :*
241− (int8 & notNull & default_ 8) `As` #col3 :* Nil )
242− [check (#col3 >* 0)]
243− `definitionRenders`
244− "CREATE TABLE table2\
245− \ (col1 serial,\
246− \ col2 text,\
247− \ col3 int8 NOT NULL DEFAULT 8,\
248− \ CHECK ((col3 > 0)));"
249− let
250− statement :: Definition '[]
251− '[ "users" :::
252− '[ "id" ::: 'Optional ('NotNull 'PGint4)
253− , "username" ::: 'Required ('NotNull 'PGtext)
254− ]
255− , "emails" :::
256− '[ "id" ::: 'Optional ('NotNull 'PGint4)
257− , "userid" ::: 'Required ('NotNull 'PGint4)
258− , "email" ::: 'Required ('NotNull 'PGtext)
259− ]
260− ]
261− statement =
262− createTable #users
263− (serial `As` #id :* (text & notNull) `As` #username :* Nil)
264− [primaryKey (Column #id :* Nil)]
265− >>>
266− createTable #emails
267− ( serial `As` #id :*
268− (integer & notNull) `As` #userid :*
269− (text & notNull) `As` #email :* Nil )
270− [ primaryKey (Column #id :* Nil)
271− , foreignKey (Column #userid :* Nil) #users (Column #id :* Nil)
272− OnDeleteCascade OnUpdateRestrict
273− , unique (Column #email :* Nil)
274− , check (#email /=* "")
275− ]
276− statement `definitionRenders`
277− "CREATE TABLE users\
278− \ (id serial, username text NOT NULL,\
279− \ PRIMARY KEY (id));\
280− \ \
281− \CREATE TABLE emails\
282− \ (id serial,\
283− \ userid integer NOT NULL,\
284− \ email text NOT NULL,\
285− \ PRIMARY KEY (id),\
286− \ FOREIGN KEY (userid) REFERENCES users (id)\
287− \ ON DELETE CASCADE ON UPDATE RESTRICT,\
288− \ UNIQUE (email),\
289− \ CHECK ((email <> E'')));"
290− it "should render DROP TABLE statements" $ do
291− let
292− statement :: Definition Tables '[]
293− statement = dropTable #table1
294− statement `definitionRenders` "DROP TABLE table1;"
295−
296−type Columns =
297− '[ "col1" ::: 'Required ('NotNull 'PGint4)
298− , "col2" ::: 'Required ('NotNull 'PGint4)
299− ]
300−type Tables = '[ "table1" ::: Columns ]
301−type SumAndCol1 =
302− '[ "sum" ::: 'Required ('NotNull 'PGint4)
303− , "col1" ::: 'Required ('NotNull 'PGint4)
304− ]
305−type StudentsColumns = '["name" ::: 'Required ('NotNull 'PGtext)]
306−type StudentsTable = '["students" ::: StudentsColumns]
307−type OrderColumns =
308− '[ "orderID" ::: 'Required ('NotNull 'PGint4)
309− , "orderVal" ::: 'Required ('NotNull 'PGtext)
310− , "customerID" ::: 'Required ('NotNull 'PGint4)
311− , "shipperID" ::: 'Required ('NotNull 'PGint4)
312− ]
313−type CustomerColumns =
314− '[ "customerID" ::: 'Required ('NotNull 'PGint4)
315− , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
316− ]
317−type ShipperColumns =
318− '[ "shipperID" ::: 'Required ('NotNull 'PGint4)
319− , "shipperVal" ::: 'Required ('NotNull 'PGbool)
320− ]
321−type JoinTables =
322− '[ "orders" ::: OrderColumns
323− , "customers" ::: CustomerColumns
324− , "shippers" ::: ShipperColumns
325− ]
326−type ValueColumns =
327− '[ "orderVal" ::: 'Required ('NotNull 'PGtext)
328− , "customerVal" ::: 'Required ('NotNull 'PGfloat4)
329− , "shipperVal" ::: 'Required ('NotNull 'PGbool)
330− ]