on dev help sign in
readmes
commitb877eee552c92c6fce3e4a1cf6c6e418c7b97cc7 authorEitan Chatav <eitan.chatav@gmail.com> dateMon 19 Mar 2018, 15:07 (9 years ago) parent409e045
2 files changed+346−196
README.md+173−98 squeal-postgresql/README.md+173−98
README.md+173−98
@@ -17,148 +17,223 @@
1717## usage
1818
1919Squeal is a deep embedding of PostgreSQL in Haskell. Let's see an example!
20−
21−First, we need some language extensions because Squeal uses modern GHC features.
22−
20+First, we need some language extensions because Squeal uses modern GHC
21+features.
2322```haskell
24−{-# LANGUAGE
25− DataKinds
26− , DeriveGeneric
27− , OverloadedLabels
28− , OverloadedStrings
29− , TypeApplications
30− , TypeOperators
31−#-}
23+>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
24+>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
3225```
3326
34−Here comes the Main module and imports.
27+We'll need some imports.
3528
3629```haskell
37−module Main (main) where
38−
39−import Control.Monad.Base (liftBase)
40−import Data.Int (Int32)
41−import Data.Text (Text)
42−
43−import Squeal.PostgreSQL
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
4435```
4536
4637We'll use generics to easily convert between Haskell and PostgreSQL values.
4738
4839```haskell
49−import qualified Generics.SOP as SOP
50−import qualified GHC.Generics as GHC
40+>>> import qualified Generics.SOP as SOP
41+>>> import qualified GHC.Generics as GHC
5142```
5243
53−The first step is to define the schema of our database. This is where we use DataKinds and TypeOperators. The schema consists of a type-level list of tables, a `:::` pairing of a type level string or `Symbol` and a list a columns, itself a `:::` pairing of a `Symbol` and a `ColumnType`. The `ColumnType` describes the PostgreSQL type of the column as well as whether or not it may contain `NULL` and whether or not inserts and updates can use a `DEFAULT`. For our schema, we'll define two tables, a users table and an emails table.
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.
5452
5553```haskell
54+>>> :{
5655type Schema =
57− '[ "users" :::
58− '[ "id" ::: 'Optional ('NotNull 'PGint4)
59− , "name" ::: 'Required ('NotNull 'PGtext)
56+ '[ "users" :::
57+ '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
58+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
59+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext
6060 ]
6161 , "emails" :::
62− '[ "id" ::: 'Optional ('NotNull 'PGint4)
63− , "user_id" ::: 'Required ('NotNull 'PGint4)
64− , "email" ::: 'Required ('Null 'PGtext)
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
6568 ]
6669 ]
70+:}
6771```
6872
69−Next, we'll write Definitions to set up and tear down the schema. In Squeal, a Definition is a `createTable`, `alterTable` or `dropTable` command and has two type parameters, corresponding to the schema before being run and the schema after. We can compose definitions using `>>>`. Here and in the rest of our commands we make use of overloaded labels to refer to named tables and columns in our schema.
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.
7079
7180```haskell
72−setup :: Definition '[] Schema
73−setup =
74− createTable #users
75− ( serial `As` #id :*
76− (text & notNull) `As` #name :* Nil )
77− [ primaryKey (Column #id :* Nil) ]
78− >>>
79− createTable #emails
80− ( serial `As` #id :*
81− (int & notNull) `As` #user_id :*
82− text `As` #email :* Nil )
83− [ primaryKey (Column #id :* Nil)
84− , foreignKey (Column #user_id :* Nil) #users (Column #id :* Nil)
85− OnDeleteCascade OnUpdateCascade ]
86−```
87−
88−Notice that setup starts with an empty schema `'[]` and produces `Schema`. In our `createTable` commands we included `TableConstraint`s to define primary and foreign keys, making them somewhat complex. Our tear down `Definition` is simpler.
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.
89100
90101```haskell
91−teardown :: Definition Schema '[]
92−teardown = dropTable #emails >>> dropTable #users
102+>>> renderDefinition setup
103+```
104+```sql
105+"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);"
93106```
94107
95−Next, we'll write `Manipulation`s to insert data into our two tables. A `Manipulation` is an `insertInto`, `update` or `deleteFrom` command and has three type parameters, the schema it refers to, a list of parameters it can take as input, and a list of columns it produces as output. When we insert into the users table, we will need a parameter for the name field but not for the id field. Since it's optional, we can use a default value. However, since the emails table refers to the users table, we will need to retrieve the user id that the insert generates and insert it into the emails table. Take a careful look at the type and definition of both of our inserts.
108+Notice that @setup@ starts with an empty schema @'[]@ and produces @Schema@.
109+In our `createTable` commands we included `TableConstraint`s to define
110+primary and foreign keys, making them somewhat complex. Our tear down
111+`Definition` is simpler.
96112
97113```haskell
98−insertUser :: Manipulation Schema
99− '[ 'Required ('NotNull 'PGtext)]
100− '[ "fromOnly" ::: 'Required ('NotNull 'PGint4) ]
101−insertUser = insertInto #users
102− ( Values (def `As` #id :* param @1 `As` #name :* Nil) [] )
103− OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
104−
105−insertEmail :: Manipulation Schema
106− '[ 'Required ('NotNull 'PGint4), 'Required ('Null 'PGtext)] '[]
107−insertEmail = insertInto #emails ( Values
108− ( def `As` #id :*
109− param @1 `As` #user_id :*
110− param @2 `As` #email :* Nil) [] )
111− OnConflictDoNothing (Returning Nil)
114+>>> :{
115+let
116+ teardown :: Definition Schema '[]
117+ teardown = dropTable #emails >>> dropTable #users
118+:}
119+>>> renderDefinition teardown
120+```
121+```sql
122+"DROP TABLE emails; DROP TABLE users;"
112123```
113124
114−Next we write a `Query` to retrieve users from the database. We're not interested in the ids here, just the usernames and email addresses. We need to use an inner join to get the right result. A `Query` is like a `Manipulation` with the same kind of type parameters.
125+Next, we'll write `Manipulation`s to insert data into our two tables.
126+A `Manipulation` is a `insertInto`, `update` or `deleteFrom` command and
127+has three type parameters, the schema it refers to, a list of parameters
128+it can take as input, and a list of columns it produces as output. When
129+we insert into the users table, we will need a parameter for the @name@
130+field but not for the @id@ field. Since it's optional, we can use a default
131+value. However, since the emails table refers to the users table, we will
132+need to retrieve the user id that the insert generates and insert it into
133+the emails table. Take a careful look at the type and definition of both
134+of our inserts.
115135
116136```haskell
117−getUsers :: Query Schema '[]
118− '[ "userName" ::: 'Required ('NotNull 'PGtext)
119− , "userEmail" ::: 'Required ('Null 'PGtext) ]
120−getUsers = select
121− (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
122− ( from (Table (#users `As` #u)
123− & InnerJoin (Table (#emails `As` #e))
124− (#u ! #id .== #e ! #user_id)) )
137+>>> :{
138+let
139+ insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ]
140+ '[ "fromOnly" ::: 'NotNull 'PGint4 ]
141+ insertUser = insertRow #users
142+ (Default `As` #id :* Set (param @1) `As` #name :* Nil)
143+ OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
144+:}
145+>>> :{
146+let
147+ insertEmail :: Manipulation Schema '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
148+ insertEmail = insertRow #emails
149+ ( Default `As` #id :*
150+ Set (param @1) `As` #user_id :*
151+ Set (param @2) `As` #email :* Nil )
152+ OnConflictDoNothing (Returning Nil)
153+:}
154+>>> renderManipulation insertUser
155+"INSERT INTO users (id, name) VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING URNING id AS fromOnly;"
156+>>> renderManipulation insertEmail
157+```
158+```sql
159+"INSERT INTO emails (id, user_id, email) VALUES (DEFAULT, ($1 :: int4), ($2 :: text)N CONFLICT DO NOTHING;"
125160```
126161
127−Now that we've defined the SQL side of things, we'll need a Haskell type for users. We give the type `Generic` and `HasDatatypeInfo` instances so that we can decode the rows we receive when we run getUsers. Notice that the record fields of the `User` type match the column names of `getUsers`.
162+Next we write a `Query` to retrieve users from the database. We're not
163+interested in the ids here, just the usernames and email addresses. We
164+need to use an inner join to get the right result. A `Query` is like a
165+`Manipulation` with the same kind of type parameters.
128166
129167```haskell
130−data User = User { userName :: Text, userEmail :: Maybe Text }
131− deriving (Show, GHC.Generic)
132−instance SOP.Generic User
133−instance SOP.HasDatatypeInfo User
168+>>> :{
169+let
170+ getUsers :: Query Schema '[]
171+ '[ "userName" ::: 'NotNull 'PGtext
172+ , "userEmail" ::: 'Null 'PGtext ]
173+ getUsers = select
174+ (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
175+ ( from (table (#users `As` #u)
176+ & innerJoin (table (#emails `As` #e))
177+ (#u ! #id .== #e ! #user_id)) )
178+:}
179+>>> renderQuery getUsers
180+```
181+```sql
182+"SELECT u.name AS userName, e.email AS userEmail FROM users AS u INNER JOIN emails e ON (u.id = e.user_id)"
183+```
184+Now that we've defined the SQL side of things, we'll need a Haskell type
185+for users. We give the type `Generics.SOP.Generic` and
186+`Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
187+we receive when we run @getUsers@. Notice that the record fields of the
188+@User@ type match the column names of @getUsers@.
189+
190+```haskell
191+>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, .Generic)
192+>>> instance SOP.Generic User
193+>>> instance SOP.HasDatatypeInfo User
134194```
135195
136196Let's also create some users to add to the database.
137197
138198```haskell
139−users :: [User]
140−users =
141− [ User "Alice" (Just "alice@gmail.com")
142− , User "Bob" Nothing
143− , User "Carole" (Just "carole@hotmail.com")
144− ]
199+>>> :{
200+let
201+ users :: [User]
202+ users =
203+ [ User "Alice" (Just "alice@gmail.com")
204+ , User "Bob" Nothing
205+ , User "Carole" (Just "carole@hotmail.com")
206+ ]
207+:}
145208```
146209
147−Now we can put together all the pieces into a program. The program connects to the database, sets up the schema, inserts the user data (using prepared statements as an optimization), queries the user data and prints it out and finally closes the connection. We can thread the changing schema information through by using the indexed `PQ` monad transformer and when the schema doesn't change we can use `Monad` and `MonadPQ` functionality.
210+Now we can put together all the pieces into a program. The program
211+connects to the database, sets up the schema, inserts the user data
212+(using prepared statements as an optimization), queries the user
213+data and prints it out and finally closes the connection. We can thread
214+the changing schema information through by using the indexed `PQ` monad
215+transformer and when the schema doesn't change we can use `Monad` and
216+`MonadPQ` functionality.
148217
149218```haskell
150−main :: IO ()
151−main = void $
152− withConnection "host=localhost port=5432 dbname=exampledb" . runPQ $
153− define setup
154− & pqThen session
155− & thenDefine teardown
156− where
157− session = do
158− idResults <- traversePrepared insertUser (Only . userName <$> users)
159− ids <- traverse (fmap fromOnly . getRow (RowNumber 0)) idResults
160− traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
161− usersResult <- runQuery getUsers
162− usersRows <- getRows usersResult
163− liftBase $ print (usersRows :: [User])
219+>>> :{
220+let
221+ session :: PQ Schema Schema IO ()
222+ session = do
223+ idResults <- traversePrepared insertUser (Only . userName <$> users)
224+ ids <- traverse (fmap fromOnly . getRow (RowNumber 0)) idResults
225+ traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
226+ usersResult <- runQuery getUsers
227+ usersRows <- getRows usersResult
228+ liftBase $ print (usersRows :: [User])
229+:}
230+>>> :{
231+void . withConnection "host=localhost port=5432 dbname=exampledb" $
232+ define setup
233+ & pqThen session
234+ & thenDefine teardown
235+:}
236+```
237+```sql
238+[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just role@hotmail.com"}]
164239```
squeal-postgresql/README.md+173−98
@@ -17,148 +17,223 @@
1717## usage
1818
1919Squeal is a deep embedding of PostgreSQL in Haskell. Let's see an example!
20−
21−First, we need some language extensions because Squeal uses modern GHC features.
22−
20+First, we need some language extensions because Squeal uses modern GHC
21+features.
2322```haskell
24−{-# LANGUAGE
25− DataKinds
26− , DeriveGeneric
27− , OverloadedLabels
28− , OverloadedStrings
29− , TypeApplications
30− , TypeOperators
31−#-}
23+>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
24+>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
3225```
3326
34−Here comes the Main module and imports.
27+We'll need some imports.
3528
3629```haskell
37−module Main (main) where
38−
39−import Control.Monad.Base (liftBase)
40−import Data.Int (Int32)
41−import Data.Text (Text)
42−
43−import Squeal.PostgreSQL
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
4435```
4536
4637We'll use generics to easily convert between Haskell and PostgreSQL values.
4738
4839```haskell
49−import qualified Generics.SOP as SOP
50−import qualified GHC.Generics as GHC
40+>>> import qualified Generics.SOP as SOP
41+>>> import qualified GHC.Generics as GHC
5142```
5243
53−The first step is to define the schema of our database. This is where we use DataKinds and TypeOperators. The schema consists of a type-level list of tables, a `:::` pairing of a type level string or `Symbol` and a list a columns, itself a `:::` pairing of a `Symbol` and a `ColumnType`. The `ColumnType` describes the PostgreSQL type of the column as well as whether or not it may contain `NULL` and whether or not inserts and updates can use a `DEFAULT`. For our schema, we'll define two tables, a users table and an emails table.
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.
5452
5553```haskell
54+>>> :{
5655type Schema =
57− '[ "users" :::
58− '[ "id" ::: 'Optional ('NotNull 'PGint4)
59− , "name" ::: 'Required ('NotNull 'PGtext)
56+ '[ "users" :::
57+ '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
58+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
59+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext
6060 ]
6161 , "emails" :::
62− '[ "id" ::: 'Optional ('NotNull 'PGint4)
63− , "user_id" ::: 'Required ('NotNull 'PGint4)
64− , "email" ::: 'Required ('Null 'PGtext)
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
6568 ]
6669 ]
70+:}
6771```
6872
69−Next, we'll write Definitions to set up and tear down the schema. In Squeal, a Definition is a `createTable`, `alterTable` or `dropTable` command and has two type parameters, corresponding to the schema before being run and the schema after. We can compose definitions using `>>>`. Here and in the rest of our commands we make use of overloaded labels to refer to named tables and columns in our schema.
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.
7079
7180```haskell
72−setup :: Definition '[] Schema
73−setup =
74− createTable #users
75− ( serial `As` #id :*
76− (text & notNull) `As` #name :* Nil )
77− [ primaryKey (Column #id :* Nil) ]
78− >>>
79− createTable #emails
80− ( serial `As` #id :*
81− (int & notNull) `As` #user_id :*
82− text `As` #email :* Nil )
83− [ primaryKey (Column #id :* Nil)
84− , foreignKey (Column #user_id :* Nil) #users (Column #id :* Nil)
85− OnDeleteCascade OnUpdateCascade ]
86−```
87−
88−Notice that setup starts with an empty schema `'[]` and produces `Schema`. In our `createTable` commands we included `TableConstraint`s to define primary and foreign keys, making them somewhat complex. Our tear down `Definition` is simpler.
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.
89100
90101```haskell
91−teardown :: Definition Schema '[]
92−teardown = dropTable #emails >>> dropTable #users
102+>>> renderDefinition setup
103+```
104+```sql
105+"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);"
93106```
94107
95−Next, we'll write `Manipulation`s to insert data into our two tables. A `Manipulation` is an `insertInto`, `update` or `deleteFrom` command and has three type parameters, the schema it refers to, a list of parameters it can take as input, and a list of columns it produces as output. When we insert into the users table, we will need a parameter for the name field but not for the id field. Since it's optional, we can use a default value. However, since the emails table refers to the users table, we will need to retrieve the user id that the insert generates and insert it into the emails table. Take a careful look at the type and definition of both of our inserts.
108+Notice that @setup@ starts with an empty schema @'[]@ and produces @Schema@.
109+In our `createTable` commands we included `TableConstraint`s to define
110+primary and foreign keys, making them somewhat complex. Our tear down
111+`Definition` is simpler.
96112
97113```haskell
98−insertUser :: Manipulation Schema
99− '[ 'Required ('NotNull 'PGtext)]
100− '[ "fromOnly" ::: 'Required ('NotNull 'PGint4) ]
101−insertUser = insertInto #users
102− ( Values (def `As` #id :* param @1 `As` #name :* Nil) [] )
103− OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
104−
105−insertEmail :: Manipulation Schema
106− '[ 'Required ('NotNull 'PGint4), 'Required ('Null 'PGtext)] '[]
107−insertEmail = insertInto #emails ( Values
108− ( def `As` #id :*
109− param @1 `As` #user_id :*
110− param @2 `As` #email :* Nil) [] )
111− OnConflictDoNothing (Returning Nil)
114+>>> :{
115+let
116+ teardown :: Definition Schema '[]
117+ teardown = dropTable #emails >>> dropTable #users
118+:}
119+>>> renderDefinition teardown
120+```
121+```sql
122+"DROP TABLE emails; DROP TABLE users;"
112123```
113124
114−Next we write a `Query` to retrieve users from the database. We're not interested in the ids here, just the usernames and email addresses. We need to use an inner join to get the right result. A `Query` is like a `Manipulation` with the same kind of type parameters.
125+Next, we'll write `Manipulation`s to insert data into our two tables.
126+A `Manipulation` is a `insertInto`, `update` or `deleteFrom` command and
127+has three type parameters, the schema it refers to, a list of parameters
128+it can take as input, and a list of columns it produces as output. When
129+we insert into the users table, we will need a parameter for the @name@
130+field but not for the @id@ field. Since it's optional, we can use a default
131+value. However, since the emails table refers to the users table, we will
132+need to retrieve the user id that the insert generates and insert it into
133+the emails table. Take a careful look at the type and definition of both
134+of our inserts.
115135
116136```haskell
117−getUsers :: Query Schema '[]
118− '[ "userName" ::: 'Required ('NotNull 'PGtext)
119− , "userEmail" ::: 'Required ('Null 'PGtext) ]
120−getUsers = select
121− (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
122− ( from (Table (#users `As` #u)
123− & InnerJoin (Table (#emails `As` #e))
124− (#u ! #id .== #e ! #user_id)) )
137+>>> :{
138+let
139+ insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ]
140+ '[ "fromOnly" ::: 'NotNull 'PGint4 ]
141+ insertUser = insertRow #users
142+ (Default `As` #id :* Set (param @1) `As` #name :* Nil)
143+ OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
144+:}
145+>>> :{
146+let
147+ insertEmail :: Manipulation Schema '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
148+ insertEmail = insertRow #emails
149+ ( Default `As` #id :*
150+ Set (param @1) `As` #user_id :*
151+ Set (param @2) `As` #email :* Nil )
152+ OnConflictDoNothing (Returning Nil)
153+:}
154+>>> renderManipulation insertUser
155+"INSERT INTO users (id, name) VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING URNING id AS fromOnly;"
156+>>> renderManipulation insertEmail
157+```
158+```sql
159+"INSERT INTO emails (id, user_id, email) VALUES (DEFAULT, ($1 :: int4), ($2 :: text)N CONFLICT DO NOTHING;"
125160```
126161
127−Now that we've defined the SQL side of things, we'll need a Haskell type for users. We give the type `Generic` and `HasDatatypeInfo` instances so that we can decode the rows we receive when we run getUsers. Notice that the record fields of the `User` type match the column names of `getUsers`.
162+Next we write a `Query` to retrieve users from the database. We're not
163+interested in the ids here, just the usernames and email addresses. We
164+need to use an inner join to get the right result. A `Query` is like a
165+`Manipulation` with the same kind of type parameters.
128166
129167```haskell
130−data User = User { userName :: Text, userEmail :: Maybe Text }
131− deriving (Show, GHC.Generic)
132−instance SOP.Generic User
133−instance SOP.HasDatatypeInfo User
168+>>> :{
169+let
170+ getUsers :: Query Schema '[]
171+ '[ "userName" ::: 'NotNull 'PGtext
172+ , "userEmail" ::: 'Null 'PGtext ]
173+ getUsers = select
174+ (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
175+ ( from (table (#users `As` #u)
176+ & innerJoin (table (#emails `As` #e))
177+ (#u ! #id .== #e ! #user_id)) )
178+:}
179+>>> renderQuery getUsers
180+```
181+```sql
182+"SELECT u.name AS userName, e.email AS userEmail FROM users AS u INNER JOIN emails e ON (u.id = e.user_id)"
183+```
184+Now that we've defined the SQL side of things, we'll need a Haskell type
185+for users. We give the type `Generics.SOP.Generic` and
186+`Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
187+we receive when we run @getUsers@. Notice that the record fields of the
188+@User@ type match the column names of @getUsers@.
189+
190+```haskell
191+>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, .Generic)
192+>>> instance SOP.Generic User
193+>>> instance SOP.HasDatatypeInfo User
134194```
135195
136196Let's also create some users to add to the database.
137197
138198```haskell
139−users :: [User]
140−users =
141− [ User "Alice" (Just "alice@gmail.com")
142− , User "Bob" Nothing
143− , User "Carole" (Just "carole@hotmail.com")
144− ]
199+>>> :{
200+let
201+ users :: [User]
202+ users =
203+ [ User "Alice" (Just "alice@gmail.com")
204+ , User "Bob" Nothing
205+ , User "Carole" (Just "carole@hotmail.com")
206+ ]
207+:}
145208```
146209
147−Now we can put together all the pieces into a program. The program connects to the database, sets up the schema, inserts the user data (using prepared statements as an optimization), queries the user data and prints it out and finally closes the connection. We can thread the changing schema information through by using the indexed `PQ` monad transformer and when the schema doesn't change we can use `Monad` and `MonadPQ` functionality.
210+Now we can put together all the pieces into a program. The program
211+connects to the database, sets up the schema, inserts the user data
212+(using prepared statements as an optimization), queries the user
213+data and prints it out and finally closes the connection. We can thread
214+the changing schema information through by using the indexed `PQ` monad
215+transformer and when the schema doesn't change we can use `Monad` and
216+`MonadPQ` functionality.
148217
149218```haskell
150−main :: IO ()
151−main = void $
152− withConnection "host=localhost port=5432 dbname=exampledb" . runPQ $
153− define setup
154− & pqThen session
155− & thenDefine teardown
156− where
157− session = do
158− idResults <- traversePrepared insertUser (Only . userName <$> users)
159− ids <- traverse (fmap fromOnly . getRow (RowNumber 0)) idResults
160− traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
161− usersResult <- runQuery getUsers
162− usersRows <- getRows usersResult
163− liftBase $ print (usersRows :: [User])
219+>>> :{
220+let
221+ session :: PQ Schema Schema IO ()
222+ session = do
223+ idResults <- traversePrepared insertUser (Only . userName <$> users)
224+ ids <- traverse (fmap fromOnly . getRow (RowNumber 0)) idResults
225+ traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
226+ usersResult <- runQuery getUsers
227+ usersRows <- getRows usersResult
228+ liftBase $ print (usersRows :: [User])
229+:}
230+>>> :{
231+void . withConnection "host=localhost port=5432 dbname=exampledb" $
232+ define setup
233+ & pqThen session
234+ & thenDefine teardown
235+:}
236+```
237+```sql
238+[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just role@hotmail.com"}]
164239```