on dev help sign in
docs
commitd138440df936bac8d8d0801b4f85b3ea7a0f60c8 authorEitan Chatav <eitan@Eitans-MacBook-Pro.local> dateThu 25 Apr 2019, 11:33 (7 years ago) parentef30eff
7 files changed+126−156
README.md+59−70 squeal-postgresql/src/Squeal/PostgreSQL.hs+50−81 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Math.hs−1 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Subquery.hs−1 squeal-postgresql/src/Squeal/PostgreSQL/Expression/Text.hs−1 squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+14−1 squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+3−1
README.md+59−70
@@ -47,6 +47,10 @@ composable and cover a large portion of SQL.
4747* views
4848* array, composite and enumerated types
4949* json operations
50+* multischema support
51+* correlated subqueries
52+* window functions
53+* text search
5054
5155## installation
5256
@@ -72,19 +76,17 @@ First, we need some language extensions because Squeal uses modern GHC
7276features.
7377
7478```Haskell
75−>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
76−>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
79+>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels -XFlexibleContexts
80+>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators -XGADTs
7781```
7882
7983We'll need some imports.
8084
8185```Haskell
82−>>> import Control.Monad (void)
8386>>> import Control.Monad.IO.Class (liftIO)
8487>>> import Data.Int (Int32)
8588>>> import Data.Text (Text)
8689>>> import Squeal.PostgreSQL
87−>>> import Squeal.PostgreSQL.Render
8890```
8991
9092We'll use generics to easily convert between Haskell and PostgreSQL values.
@@ -99,21 +101,21 @@ we use `DataKinds` and `TypeOperators`.
99101
100102```Haskell
101103>>> :{
104+type UsersColumns =
105+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
106+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext ]
107+type UsersConstraints = '[ "pk_users" ::: 'PrimaryKey '["id"] ]
108+type EmailsColumns =
109+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
110+ , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
111+ , "email" ::: 'NoDef :=> 'Null 'PGtext ]
112+type EmailsConstraints =
113+ '[ "pk_emails" ::: 'PrimaryKey '["id"]
114+ , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"] ]
102115type Schema =
103− '[ "users" ::: 'Table (
104− '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
105− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
106− , "name" ::: 'NoDef :=> 'NotNull 'PGtext
107− ])
108− , "emails" ::: 'Table (
109− '[ "pk_emails" ::: 'PrimaryKey '["id"]
110− , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
111− ] :=>
112− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
113− , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
114− , "email" ::: 'NoDef :=> 'Null 'PGtext
115− ])
116− ]
116+ '[ "users" ::: 'Table (UsersConstraints :=> UsersColumns)
117+ , "emails" ::: 'Table (EmailsConstraints :=> EmailsColumns) ]
118+type Schemas = Public Schema
117119:}
118120```
119121
@@ -137,7 +139,7 @@ labels to refer to named tables and columns in our schema.
137139```Haskell
138140>>> :{
139141let
140− setup :: Definition '[] Schema
142+ setup :: Definition (Public '[]) Schemas
141143 setup =
142144 createTable #users
143145 ( serial `as` #id :*
@@ -169,7 +171,7 @@ primary and foreign keys, making them somewhat complex. Our `teardown`
169171```Haskell
170172>>> :{
171173let
172− teardown :: Definition Schema '[]
174+ teardown :: Definition Schemas (Public '[])
173175 teardown = dropTable #emails >>> dropTable #users
174176:}
175177
@@ -178,54 +180,54 @@ DROP TABLE "emails";
178180DROP TABLE "users";
179181```
180182
181−Next, we'll write `Manipulation`s to insert data into our two tables.
182−A `Manipulation` like `insertRow`, `update` or `deleteFrom`
183−has three type parameters, the schema it refers to, a list of parameters
184−it can take as input, and a list of columns it produces as output. When
185−we insert into the users table, we will need a parameter for the `name`
186−field but not for the `id` field. Since it's serial, we can use a default
183+We'll need a Haskell type for @User@s. We give the type `Generics.SOP.Generic` and
184+`Generics.SOP.HasDatatypeInfo` instances so that we can encode and decode @User@s.
185+
186+```Haskell
187+>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, GHC.Generic)
188+>>> instance SOP.Generic User
189+>>> instance SOP.HasDatatypeInfo User
190+```
191+
192+Next, we'll write `Manipulation_`s to insert @User@s into our two tables.
193+A `Manipulation_` like `insertInto`, `update` or `deleteFrom`
194+has three type parameters, the schemas it refers to, input parameters
195+and an output row. When
196+we insert into the users table, we will need a parameter for the @name@
197+field but not for the @id@ field. Since it's serial, we can use a default
187198value. However, since the emails table refers to the users table, we will
188199need to retrieve the user id that the insert generates and insert it into
189−the emails table. Take a careful look at the type and definition of both
190−of our inserts.
200+the emails table. We can do this in a single `Manipulation_` by using a
201+`with` statement.
191202
192203```Haskell
193204>>> :{
194205let
195− insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ] '[ "fromOnly" ::: 'NotNull 'PGint4 ]
196− insertUser = insertRow #users
197− (Default `as` #id :* Set (param @1) `as` #name)
198− OnConflictDoNothing (Returning (#id `as` #fromOnly))
199−:}
200−
201−>>> :{
202−let
203− insertEmail :: Manipulation Schema '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
204− insertEmail = insertRow #emails
205− ( Default `as` #id :*
206− Set (param @1) `as` #user_id :*
207− Set (param @2) `as` #email )
208− OnConflictDoNothing (Returning Nil)
206+ insertUser :: Manipulation_ Schemas User ()
207+ insertUser = with (u `as` #u) e
208+ where
209+ u = insertInto #users
210+ (Values_ (Default `as` #id :* Set (param @1) `as` #name))
211+ OnConflictDoRaise (Returning_ (#id :* param @2 `as` #email))
212+ e = insertInto_ #emails $ Select
213+ (Default `as` #id :* Set (#u ! #id) `as` #user_id :* Set (#u ! #email) `as` #email)
214+ (from (common #u))
209215:}
210216
211217>>> printSQL insertUser
212−INSERT INTO "users" ("id", "name") VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING RETURNING "id" AS "fromOnly"
213−>>> printSQL insertEmail
214−INSERT INTO "emails" ("id", "user_id", "email") VALUES (DEFAULT, ($1 :: int4), ($2 :: text)) ON CONFLICT DO NOTHING
218+WITH "u" AS (INSERT INTO "users" ("id", "name") VALUES (DEFAULT, ($1 :: text)) RETURNING "id" AS "id", ($2 :: text) AS "email") INSERT INTO "emails" ("user_id", "email") SELECT "u"."id", "u"."email" FROM "u" AS "u"
215219```
216220
217−Next we write a `Query` to retrieve users from the database. We're not
221+Next we write a `Query_` to retrieve users from the database. We're not
218222interested in the ids here, just the usernames and email addresses. We
219−need to use an inner join to get the right result. A `Query` is like a
220−`Manipulation` with the same kind of type parameters.
223+need to use an `innerJoin` to get the right result. A `Query_` is like a
224+`Manipulation_` with the same kind of type parameters.
221225
222226```Haskell
223227>>> :{
224228let
225− getUsers :: Query Schema '[]
226− '[ "userName" ::: 'NotNull 'PGtext
227− , "userEmail" ::: 'Null 'PGtext ]
228− getUsers = select
229+ getUsers :: Query_ Schemas () User
230+ getUsers = select_
229231 (#u ! #name `as` #userName :* #e ! #email `as` #userEmail)
230232 ( from (table (#users `as` #u)
231233 & innerJoin (table (#emails `as` #e))
@@ -236,19 +238,7 @@ let
236238SELECT "u"."name" AS "userName", "e"."email" AS "userEmail" FROM "users" AS "u" INNER JOIN "emails" AS "e" ON ("u"."id" = "e"."user_id")
237239```
238240
239−Now that we've defined the SQL side of things, we'll need a Haskell type
240−for users. We give the type `Generics.SOP.Generic` and
241−`Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
242−we receive when we run `getUsers`. Notice that the record fields of the
243−`User` type match the column names of `getUsers`.
244−
245−```Haskell
246−>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, GHC.Generic)
247−>>> instance SOP.Generic User
248−>>> instance SOP.HasDatatypeInfo User
249−```
250−
251−Let's also create some users to add to the database.
241+Let's create some users to add to the database.
252242
253243```Haskell
254244>>> :{
@@ -273,19 +263,18 @@ transformer and when the schema doesn't change we can use `Monad` and
273263```Haskell
274264>>> :{
275265let
276− session :: PQ Schema Schema IO ()
266+ session :: PQ Schemas Schemas IO ()
277267 session = do
278− idResults <- traversePrepared insertUser (Only . userName <$> users)
279− ids <- traverse (fmap fromOnly . getRow 0) idResults
280− traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
268+ _ <- traversePrepared_ insertUser users
281269 usersResult <- runQuery getUsers
282270 usersRows <- getRows usersResult
283271 liftIO $ print (usersRows :: [User])
284272in
285− void . withConnection "host=localhost port=5432 dbname=exampledb" $
273+ withConnection "host=localhost port=5432 dbname=exampledb" $
286274 define setup
287275 & pqThen session
288276 & pqThen (define teardown)
289277:}
290278[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "carole@hotmail.com"}]
279+-}
291280```
squeal-postgresql/src/Squeal/PostgreSQL.hs+50−81
@@ -11,17 +11,15 @@ Let's see an example!
1111First, we need some language extensions because Squeal uses modern GHC
1212features.
1313
14−>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
14+>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels -XFlexibleContexts
1515>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators -XGADTs
1616
1717We'll need some imports.
1818
19−>>> import Control.Monad (void)
2019>>> import Control.Monad.IO.Class (liftIO)
2120>>> import Data.Int (Int32)
2221>>> import Data.Text (Text)
2322>>> import Squeal.PostgreSQL
24−>>> import Squeal.PostgreSQL.Render
2523
2624We'll use generics to easily convert between Haskell and PostgreSQL values.
2725
@@ -104,26 +102,24 @@ let
104102DROP TABLE "emails";
105103DROP TABLE "users";
106104
107−Next, we'll write `Manipulation`s to insert data into our two tables.
108−A `Manipulation` like `insertInto`, `update` or `deleteFrom`
109−has three type parameters, the schema it refers to, a list of parameters
110−it can take as input, and a list of columns it produces as output. When
111−we insert into the users table, we will need a parameter for the @name@
112−field but not for the @id@ field. Since it's serial, we can use a default
113−value. However, since the emails table refers to the users table, we will
114−need to retrieve the user id that the insert generates and insert it into
115−the emails table. Take a careful look at the type and definition of both
116−of our inserts.
117−
118−We'll need a Haskell type for users. We give the type `Generics.SOP.Generic` and
119−`Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
120−we receive when we run @getUsers@. Notice that the record fields of the
121−@User@ type match the column names of @getUsers@.
105+We'll need a Haskell type for @User@s. We give the type `Generics.SOP.Generic` and
106+`Generics.SOP.HasDatatypeInfo` instances so that we can encode and decode @User@s.
122107
123108>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, GHC.Generic)
124109>>> instance SOP.Generic User
125110>>> instance SOP.HasDatatypeInfo User
126111
112+Next, we'll write `Manipulation_`s to insert @User@s into our two tables.
113+A `Manipulation_` like `insertInto`, `update` or `deleteFrom`
114+has three type parameters, the schemas it refers to, input parameters
115+and an output row. When
116+we insert into the users table, we will need a parameter for the @name@
117+field but not for the @id@ field. Since it's serial, we can use a default
118+value. However, since the emails table refers to the users table, we will
119+need to retrieve the user id that the insert generates and insert it into
120+the emails table. We can do this in a single `Manipulation_` by using a
121+`with` statement.
122+
127123>>> :{
128124let
129125 insertUser :: Manipulation_ Schemas User ()
@@ -140,10 +136,10 @@ let
140136>>> printSQL insertUser
141137WITH "u" AS (INSERT INTO "users" ("id", "name") VALUES (DEFAULT, ($1 :: text)) RETURNING "id" AS "id", ($2 :: text) AS "email") INSERT INTO "emails" ("user_id", "email") SELECT "u"."id", "u"."email" FROM "u" AS "u"
142138
143−Next we write a `Query` to retrieve users from the database. We're not
139+Next we write a `Query_` to retrieve users from the database. We're not
144140interested in the ids here, just the usernames and email addresses. We
145−need to use an inner join to get the right result. A `Query` is like a
146−`Manipulation` with the same kind of type parameters.
141+need to use an `innerJoin` to get the right result. A `Query_` is like a
142+`Manipulation_` with the same kind of type parameters.
147143
148144>>> :{
149145let
@@ -187,69 +183,42 @@ let
187183 usersRows <- getRows usersResult
188184 liftIO $ print (usersRows :: [User])
189185in
190− void . withConnection "host=localhost port=5432 dbname=exampledb" $
186+ withConnection "host=localhost port=5432 dbname=exampledb" $
191187 define setup
192188 & pqThen session
193189 & pqThen (define teardown)
194190:}
195191[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "carole@hotmail.com"}]
196192-}
197−module Squeal.PostgreSQL
198− ( module Squeal.PostgreSQL.Alias
199− , module Squeal.PostgreSQL.Binary
200− , module Squeal.PostgreSQL.Definition
201− , module Squeal.PostgreSQL.Expression
202− , module Squeal.PostgreSQL.Expression.Aggregate
203− , module Squeal.PostgreSQL.Expression.Collection
204− , module Squeal.PostgreSQL.Expression.Comparison
205− , module Squeal.PostgreSQL.Expression.Json
206− , module Squeal.PostgreSQL.Expression.Literal
207− , module Squeal.PostgreSQL.Expression.Logic
208− , module Squeal.PostgreSQL.Expression.Math
209− , module Squeal.PostgreSQL.Expression.Null
210− , module Squeal.PostgreSQL.Expression.Parameter
211− , module Squeal.PostgreSQL.Expression.SetOf
212− , module Squeal.PostgreSQL.Expression.Sort
213− , module Squeal.PostgreSQL.Expression.Subquery
214− , module Squeal.PostgreSQL.Expression.Text
215− , module Squeal.PostgreSQL.Expression.TextSearch
216− , module Squeal.PostgreSQL.Expression.Time
217− , module Squeal.PostgreSQL.Expression.Type
218− , module Squeal.PostgreSQL.Expression.Window
219− , module Squeal.PostgreSQL.List
220− , module Squeal.PostgreSQL.Manipulation
221− , module Squeal.PostgreSQL.PG
222− , module Squeal.PostgreSQL.PQ
223− , module Squeal.PostgreSQL.Query
224− , module Squeal.PostgreSQL.Schema
225− , module Squeal.PostgreSQL.Transaction
226− ) where
227−
228−import Squeal.PostgreSQL.Alias
229−import Squeal.PostgreSQL.Binary
230−import Squeal.PostgreSQL.Definition
231−import Squeal.PostgreSQL.Expression
232−import Squeal.PostgreSQL.Expression.Aggregate
233−import Squeal.PostgreSQL.Expression.Collection
234−import Squeal.PostgreSQL.Expression.Comparison
235−import Squeal.PostgreSQL.Expression.Json
236−import Squeal.PostgreSQL.Expression.Literal
237−import Squeal.PostgreSQL.Expression.Logic
238−import Squeal.PostgreSQL.Expression.Math
239−import Squeal.PostgreSQL.Expression.Null
240−import Squeal.PostgreSQL.Expression.Parameter
241−import Squeal.PostgreSQL.Expression.SetOf
242−import Squeal.PostgreSQL.Expression.Sort
243−import Squeal.PostgreSQL.Expression.Subquery
244−import Squeal.PostgreSQL.Expression.Text
245−import Squeal.PostgreSQL.Expression.TextSearch
246−import Squeal.PostgreSQL.Expression.Time
247−import Squeal.PostgreSQL.Expression.Type
248−import Squeal.PostgreSQL.Expression.Window
249−import Squeal.PostgreSQL.List
250−import Squeal.PostgreSQL.Manipulation
251−import Squeal.PostgreSQL.PG
252−import Squeal.PostgreSQL.PQ
253−import Squeal.PostgreSQL.Query
254−import Squeal.PostgreSQL.Schema
255−import Squeal.PostgreSQL.Transaction
193+module Squeal.PostgreSQL (module X) where
194+
195+import Squeal.PostgreSQL.Alias as X
196+import Squeal.PostgreSQL.Binary as X
197+import Squeal.PostgreSQL.Definition as X
198+import Squeal.PostgreSQL.Expression as X
199+import Squeal.PostgreSQL.Expression.Aggregate as X
200+import Squeal.PostgreSQL.Expression.Collection as X
201+import Squeal.PostgreSQL.Expression.Comparison as X
202+import Squeal.PostgreSQL.Expression.Json as X
203+import Squeal.PostgreSQL.Expression.Literal as X
204+import Squeal.PostgreSQL.Expression.Logic as X
205+import Squeal.PostgreSQL.Expression.Math as X
206+import Squeal.PostgreSQL.Expression.Null as X
207+import Squeal.PostgreSQL.Expression.Parameter as X
208+import Squeal.PostgreSQL.Expression.SetOf as X
209+import Squeal.PostgreSQL.Expression.Sort as X
210+import Squeal.PostgreSQL.Expression.Subquery as X
211+import Squeal.PostgreSQL.Expression.Text as X
212+import Squeal.PostgreSQL.Expression.TextSearch as X
213+import Squeal.PostgreSQL.Expression.Time as X
214+import Squeal.PostgreSQL.Expression.Type as X
215+import Squeal.PostgreSQL.Expression.Window as X
216+import Squeal.PostgreSQL.List as X
217+import Squeal.PostgreSQL.Manipulation as X
218+import Squeal.PostgreSQL.Migration as X
219+import Squeal.PostgreSQL.PG as X
220+import Squeal.PostgreSQL.PQ as X
221+import Squeal.PostgreSQL.Query as X
222+import Squeal.PostgreSQL.Render as X
223+import Squeal.PostgreSQL.Schema as X
224+import Squeal.PostgreSQL.Transaction as X
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Math.hs−1
@@ -29,7 +29,6 @@ import Squeal.PostgreSQL.Schema
2929
3030-- $setup
3131-- >>> import Squeal.PostgreSQL
32−-- >>> import Squeal.PostgreSQL.Render
3332
3433-- | >>> :{
3534-- let
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Subquery.hs−1
@@ -33,7 +33,6 @@ import Squeal.PostgreSQL.Schema
3333
3434-- $setup
3535-- >>> import Squeal.PostgreSQL
36−-- >>> import Squeal.PostgreSQL.Render
3736
3837{- |
3938The argument of `exists` is an arbitrary subquery. The subquery is evaluated
squeal-postgresql/src/Squeal/PostgreSQL/Expression/Text.hs−1
@@ -27,7 +27,6 @@ import Squeal.PostgreSQL.Schema
2727
2828-- $setup
2929-- >>> import Squeal.PostgreSQL
30−-- >>> import Squeal.PostgreSQL.Render
3130
3231-- | >>> printSQL $ lower "ARRRGGG"
3332-- lower(E'ARRRGGG')
squeal-postgresql/src/Squeal/PostgreSQL/Migration.hs+14−1
@@ -164,7 +164,20 @@ import qualified Data.Text.IO as Text (putStrLn)
164164import qualified Generics.SOP as SOP
165165import qualified GHC.Generics as GHC
166166
167−import Squeal.PostgreSQL
167+import Squeal.PostgreSQL.Alias
168+import Squeal.PostgreSQL.Binary
169+import Squeal.PostgreSQL.Definition
170+import Squeal.PostgreSQL.Expression.Comparison
171+import Squeal.PostgreSQL.Expression.Parameter
172+import Squeal.PostgreSQL.Expression.Time
173+import Squeal.PostgreSQL.Expression.Type
174+import Squeal.PostgreSQL.List
175+import Squeal.PostgreSQL.Manipulation
176+import Squeal.PostgreSQL.PG
177+import Squeal.PostgreSQL.PQ
178+import Squeal.PostgreSQL.Query
179+import Squeal.PostgreSQL.Schema
180+import Squeal.PostgreSQL.Transaction
168181
169182-- | A `Migration` is a named "isomorphism" over a given category.
170183-- It should contain an inverse pair of `up` and `down`
squeal-postgresql/src/Squeal/PostgreSQL/Schema.hs+3−1
@@ -97,9 +97,11 @@ import Squeal.PostgreSQL.Alias
9797import Squeal.PostgreSQL.List
9898import Squeal.PostgreSQL.Render
9999
100+-- $setup
101+-- >>> import Squeal.PostgreSQL
102+
100103-- | `PGType` is the promoted datakind of PostgreSQL types.
101104--
102−-- >>> import Squeal.PostgreSQL.Schema
103105-- >>> :kind 'PGbool
104106-- 'PGbool :: PGType
105107data PGType