on dev help sign in
Update README.md
commit4da998dffc54390d355d07287aef18c903bd0807 authorEitan Chatav <eitan.chatav@gmail.com> dateMon 26 Mar 2018, 18:47 (9 years ago) parent2fd3cbd
1 file changed+1−219
squeal-postgresql/README.md+1−219
squeal-postgresql/README.md+1−219
@@ -1,4 +1,4 @@
1−# squeal
1+# squeal-postgresql
22
33![squeal-icon](http://www.emoticonswallpapers.com/emotion/cute-big-pig/cute-pig-smiley-046.gif)
44
@@ -9,221 +9,3 @@
99[Hackage](https://hackage.haskell.org/package/squeal-postgresql)
1010
1111[Stackage](https://www.stackage.org/package/squeal-postgresql)
12−
13−## installation
14−
15−`stack install squeal-postgresql`
16−
17−## usage
18−
19−Squeal is a deep embedding of PostgreSQL in Haskell. Let's see an example!
20−First, we need some language extensions because Squeal uses modern GHC
21−features.
22−```haskell
23−>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
24−>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
25−```
26−
27−We'll need some imports.
28−
29−```haskell
30−>>> import Control.Monad (void)
31−>>> import Control.Monad.Base (liftBase)
32−>>> import Data.Int (Int32)
33−>>> import Data.Text (Text)
34−>>> import Squeal.PostgreSQL
35−```
36−
37−We'll use generics to easily convert between Haskell and PostgreSQL values.
38−
39−```haskell
40−>>> import qualified Generics.SOP as SOP
41−>>> import qualified GHC.Generics as GHC
42−```
43−
44−The first step is to define the schema of our database. This is where
45−we use `DataKinds` and `TypeOperators`. The schema consists of a type-level
46−list of tables, a `:::` pairing of a type level string or
47−`Symbol` and a list a columns, itself a `:::` pairing of a
48−`Symbol` and a `ColumnType`. The `ColumnType` describes the
49−PostgreSQL type of the column as well as whether or not it may contain
50−`NULL` and whether or not inserts and updates can use a `DEFAULT`. For our
51−schema, we'll define two tables, a users table and an emails table.
52−
53−```haskell
54−>>> :{
55−type Schema =
56− '[ "users" :::
57− '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
58− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
59− , "name" ::: 'NoDef :=> 'NotNull 'PGtext
60− ]
61− , "emails" :::
62− '[ "pk_emails" ::: 'PrimaryKey '["id"]
63− , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
64− ] :=>
65− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
66− , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
67− , "email" ::: 'NoDef :=> 'Null 'PGtext
68− ]
69− ]
70−:}
71−```
72−
73−Next, we'll write `Definition`s to set up and tear down the schema. In
74−Squeal, a `Definition` is a `createTable`, `alterTable` or `dropTable`
75−command and has two type parameters, corresponding to the schema
76−before being run and the schema after. We can compose definitions using
77−`>>>`. Here and in the rest of our commands we make use of overloaded
78−labels to refer to named tables and columns in our schema.
79−
80−```haskell
81−>>> :{
82−let
83− setup :: Definition '[] Schema
84− setup =
85− createTable #users
86− ( serial `As` #id :*
87− (text & notNull) `As` #name :* Nil )
88− ( primaryKey (Column #id :* Nil) `As` #pk_users :* Nil ) >>>
89− createTable #emails
90− ( serial `As` #id :*
91− (int & notNull) `As` #user_id :*
92− text `As` #email :* Nil )
93− ( primaryKey (Column #id :* Nil) `As` #pk_emails :*
94− foreignKey (Column #user_id :* Nil) #users (Column #id :* Nil)
95− OnDeleteCascade OnUpdateCascade `As` #fk_user_id :* Nil )
96−:}
97−```
98−
99−We can easily see the generated SQL is unsuprising looking.
100−
101−```haskell
102−>>> renderDefinition setup
103−"CREATE TABLE users (id serial, name text NOT NULL, CONSTRAINT pk_users PRIMARY KEY (id)); CREATE TABLE emails (id serial, user_id int NOT NULL, email text, CONSTRAINT pk_emails PRIMARY KEY (id), CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES rs (id) ON DELETE CASCADE ON UPDATE CASCADE);"
104−```
105−
106−Notice that `setup` starts with an empty schema `'[]` and produces `Schema`.
107−In our `createTable` commands we included `TableConstraint`s to define
108−primary and foreign keys, making them somewhat complex. Our tear down
109−`Definition` is simpler.
110−
111−```haskell
112−>>> :{
113−let
114− teardown :: Definition Schema '[]
115− teardown = dropTable #emails >>> dropTable #users
116−:}
117−>>> renderDefinition teardown
118−"DROP TABLE emails; DROP TABLE users;"
119−```
120−
121−Next, we'll write `Manipulation`s to insert data into our two tables.
122−A `Manipulation` is a `insertInto`, `update` or `deleteFrom` command and
123−has three type parameters, the schema it refers to, a list of parameters
124−it can take as input, and a list of columns it produces as output. When
125−we insert into the users table, we will need a parameter for the `name`
126−field but not for the `id` field. Since it's optional, we can use a default
127−value. However, since the emails table refers to the users table, we will
128−need to retrieve the user id that the insert generates and insert it into
129−the emails table. Take a careful look at the type and definition of both
130−of our inserts.
131−
132−```haskell
133−>>> :{
134−let
135− insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ]
136− '[ "fromOnly" ::: 'NotNull 'PGint4 ]
137− insertUser = insertRow #users
138− (Default `As` #id :* Set (param `1) `As` #name :* Nil)
139− OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
140−:}
141−>>> :{
142−let
143− insertEmail :: Manipulation Schema '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
144− insertEmail = insertRow #emails
145− ( Default `As` #id :*
146− Set (param `1) `As` #user_id :*
147− Set (param `2) `As` #email :* Nil )
148− OnConflictDoNothing (Returning Nil)
149−:}
150−>>> renderManipulation insertUser
151−"INSERT INTO users (id, name) VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING URNING id AS fromOnly;"
152−>>> renderManipulation insertEmail
153−"INSERT INTO emails (id, user_id, email) VALUES (DEFAULT, ($1 :: int4), ($2 :: text)N CONFLICT DO NOTHING;"
154−```
155−
156−Next we write a `Query` to retrieve users from the database. We're not
157−interested in the ids here, just the usernames and email addresses. We
158−need to use an inner join to get the right result. A `Query` is like a
159−`Manipulation` with the same kind of type parameters.
160−
161−```haskell
162−>>> :{
163−let
164− getUsers :: Query Schema '[]
165− '[ "userName" ::: 'NotNull 'PGtext
166− , "userEmail" ::: 'Null 'PGtext ]
167− getUsers = select
168− (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
169− ( from (table (#users `As` #u)
170− & innerJoin (table (#emails `As` #e))
171− (#u ! #id .== #e ! #user_id)) )
172−:}
173−>>> renderQuery getUsers
174−"SELECT u.name AS userName, e.email AS userEmail FROM users AS u INNER JOIN emails e ON (u.id = e.user_id)"
175−```
176−Now that we've defined the SQL side of things, we'll need a Haskell type
177−for users. We give the type `Generics.SOP.Generic` and
178−`Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
179−we receive when we run `getUsers`. Notice that the record fields of the
180−`User` type match the column names of `getUsers`.
181−
182−```haskell
183−>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, .Generic)
184−>>> instance SOP.Generic User
185−>>> instance SOP.HasDatatypeInfo User
186−```
187−
188−Let's also create some users to add to the database.
189−
190−```haskell
191−>>> :{
192−let
193− users :: [User]
194− users =
195− [ User "Alice" (Just "alice`gmail.com")
196− , User "Bob" Nothing
197− , User "Carole" (Just "carole`hotmail.com")
198− ]
199−:}
200−```
201−
202−Now we can put together all the pieces into a program. The program
203−connects to the database, sets up the schema, inserts the user data
204−(using prepared statements as an optimization), queries the user
205−data and prints it out and finally closes the connection. We can thread
206−the changing schema information through by using the indexed `PQ` monad
207−transformer and when the schema doesn't change we can use `Monad` and
208−`MonadPQ` functionality.
209−
210−```haskell
211−>>> :{
212−let
213− session :: PQ Schema Schema IO ()
214− session = do
215− idResults <- traversePrepared insertUser (Only . userName <$> users)
216− ids <- traverse (fmap fromOnly . getRow (RowNumber 0)) idResults
217− traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
218− usersResult <- runQuery getUsers
219− usersRows <- getRows usersResult
220− liftBase $ print (usersRows :: [User])
221−:}
222−>>> :{
223−void . withConnection "host=localhost port=5432 dbname=exampledb" $
224− define setup
225− & pqThen session
226− & thenDefine teardown
227−:}
228−[User {userName = "Alice", userEmail = Just "alice`gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just role`hotmail.com"}]
229−```