@@ -17,7 +17,7 @@ term somewhat. What I mean is that Squeal embeds both SQL terms and SQL types
17into Haskell at the term and type levels respectively. This leads to a very high level
17into Haskell at the term and type levels respectively. This leads to a very high level
18of type-safety in Squeal.
18of type-safety in Squeal.
20−But Squeal embeds not just the structured query language of SQL but also the
20+Squeal embeds not just the structured query language of SQL but also the
21data manipulation language and the data definition language; that's `SELECT`,
21data manipulation language and the data definition language; that's `SELECT`,
22`INSERT`, `UPDATE`, `DELETE`, `WITH`, `CREATE`, `DROP`, and `ALTER` commands.
22`INSERT`, `UPDATE`, `DELETE`, `WITH`, `CREATE`, `DROP`, and `ALTER` commands.
@@ -56,6 +56,7 @@ start postgres on localhost port `5432` and create a database named `exampledb`.
58Let's see an example!
58Let's see an example!
59First, we need some language extensions because Squeal uses modern GHC
60First, we need some language extensions because Squeal uses modern GHC
@@ -82,34 +83,34 @@ We'll use generics to easily convert between Haskell and PostgreSQL values.
84The first step is to define the schema of our database. This is where
85The first step is to define the schema of our database. This is where
85−we use `DataKinds` and `TypeOperators`. The schema consists of a type-level
86+we use `DataKinds` and `TypeOperators`.
86−list of tables, a `:::` pairing of a type level string or
87−`Symbol` and a list a columns, itself a `:::` pairing of a
88−`Symbol` and a `ColumnType`. The `ColumnType` describes the
89−PostgreSQL type of the column as well as whether or not it may contain
90−`NULL` and whether or not inserts and updates can use a `DEFAULT`. For our
91−schema, we'll define two tables, a users table and an emails table.
95type Schema =
90type Schema =
96 '[ "users" :::
91 '[ "users" :::
97 '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
92 '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
98− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
93+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
99 , "name" ::: 'NoDef :=> 'NotNull 'PGtext
94 , "name" ::: 'NoDef :=> 'NotNull 'PGtext
101 , "emails" :::
96 , "emails" :::
102− '[ "pk_emails" ::: 'PrimaryKey '["id"]
97+ '[ "pk_emails" ::: 'PrimaryKey '["id"]
103 , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
98 , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
105− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
100+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
106 , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
101 , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
107− , "email" ::: 'NoDef :=> 'Null 'PGtext
102+ , "email" ::: 'NoDef :=> 'Null 'PGtext
108+Notice the use of type operators. `:::` is used
109+to pair an alias `Symbol` with either a `TableType` or a `ColumnType`.
110+`:=>` is used to pair a `TableConstraint`s with a `ColumnsType`,
111+yielding a `TableType`, or to pair a `ColumnConstraint` with a `NullityType`,
112+yielding a `ColumnType`.
113Next, we'll write `Definition`s to set up and tear down the schema. In
114Next, we'll write `Definition`s to set up and tear down the schema. In
114Squeal, a `Definition` is a `createTable`, `alterTable` or `dropTable`
115Squeal, a `Definition` is a `createTable`, `alterTable` or `dropTable`
115command and has two type parameters, corresponding to the schema
116command and has two type parameters, corresponding to the schema
@@ -140,7 +141,7 @@ We can easily see the generated SQL is unsuprising looking.
141```haskell
142```haskell
142>>> renderDefinition setup
143>>> renderDefinition setup
143−"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);"
144+"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 users (id) ON DELETE CASCADE ON UPDATE CASCADE);"
146Notice that `setup` starts with an empty schema `'[]` and produces `Schema`.
147Notice that `setup` starts with an empty schema `'[]` and produces `Schema`.
@@ -159,7 +160,8 @@ let
161Next, we'll write `Manipulation`s to insert data into our two tables.
162Next, we'll write `Manipulation`s to insert data into our two tables.
163+A `Manipulation` is an `insertRow` (or other inserts), `update`
162−A `Manipulation` is a `insertInto`, `update` or `deleteFrom` command and
164+or `deleteFrom` command and
163has three type parameters, the schema it refers to, a list of parameters
165has three type parameters, the schema it refers to, a list of parameters
164it can take as input, and a list of columns it produces as output. When
166it can take as input, and a list of columns it produces as output. When
165we insert into the users table, we will need a parameter for the `name`
167we insert into the users table, we will need a parameter for the `name`
@@ -172,8 +174,7 @@ of our inserts.
172```haskell
174```haskell
175− insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ]
177+ insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ] '[ "fromOnly" ::: 'NotNull 'PGint4 ]
176− '[ "fromOnly" ::: 'NotNull 'PGint4 ]
177 insertUser = insertRow #users
178 insertUser = insertRow #users
178 (Default `As` #id :* Set (param `1) `As` #name :* Nil)
179 (Default `As` #id :* Set (param `1) `As` #name :* Nil)
179 OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
180 OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
@@ -188,9 +189,9 @@ let
188 OnConflictDoNothing (Returning Nil)
189 OnConflictDoNothing (Returning Nil)
190>>> renderManipulation insertUser
191>>> renderManipulation insertUser
191−"INSERT INTO users (id, name) VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING URNING id AS fromOnly;"
192+"INSERT INTO users (id, name) VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING RETURNING id AS fromOnly;"
192>>> renderManipulation insertEmail
193>>> renderManipulation insertEmail
193−"INSERT INTO emails (id, user_id, email) VALUES (DEFAULT, ($1 :: int4), ($2 :: text)N CONFLICT DO NOTHING;"
194+"INSERT INTO emails (id, user_id, email) VALUES (DEFAULT, ($1 :: int4), ($2 :: text)) ON CONFLICT DO NOTHING;"
196Next we write a `Query` to retrieve users from the database. We're not
197Next we write a `Query` to retrieve users from the database. We're not
@@ -202,8 +203,8 @@ need to use an inner join to get the right result. A `Query` is like a
204 getUsers :: Query Schema '[]
205 getUsers :: Query Schema '[]
205− '[ "userName" ::: 'NotNull 'PGtext
206+ '[ "userName" ::: 'NotNull 'PGtext
206− , "userEmail" ::: 'Null 'PGtext ]
207+ , "userEmail" ::: 'Null 'PGtext ]
207 getUsers = select
208 getUsers = select
208 (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
209 (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
209 ( from (table (#users `As` #u)
210 ( from (table (#users `As` #u)
@@ -211,8 +212,9 @@ let
211 (#u ! #id .== #e ! #user_id)) )
212 (#u ! #id .== #e ! #user_id)) )
213>>> renderQuery getUsers
214>>> renderQuery getUsers
214−"SELECT u.name AS userName, e.email AS userEmail FROM users AS u INNER JOIN emails e ON (u.id = e.user_id)"
215+"SELECT u.name AS userName, e.email AS userEmail FROM users AS u INNER JOIN emails AS e ON (u.id = e.user_id)"
216Now that we've defined the SQL side of things, we'll need a Haskell type
218Now that we've defined the SQL side of things, we'll need a Haskell type
217for users. We give the type `Generics.SOP.Generic` and
219for users. We give the type `Generics.SOP.Generic` and
218`Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
220`Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
@@ -220,7 +222,7 @@ we receive when we run `getUsers`. Notice that the record fields of the
220`User` type match the column names of `getUsers`.
222`User` type match the column names of `getUsers`.
222```haskell
224```haskell
223−>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, .Generic)
225+>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, GHC.Generic)
224>>> instance SOP.Generic User
226>>> instance SOP.Generic User
225>>> instance SOP.HasDatatypeInfo User
227>>> instance SOP.HasDatatypeInfo User
@@ -253,7 +255,7 @@ let
253 session :: PQ Schema Schema IO ()
255 session :: PQ Schema Schema IO ()
254 session = do
256 session = do
255 idResults <- traversePrepared insertUser (Only . userName <$> users)
257 idResults <- traversePrepared insertUser (Only . userName <$> users)
256− ids <- traverse (fmap fromOnly . getRow (RowNumber 0)) idResults
258+ ids <- traverse (fmap fromOnly . getRow 0) idResults
257 traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
259 traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
258 usersResult <- runQuery getUsers
260 usersResult <- runQuery getUsers
259 usersRows <- getRows usersResult
261 usersRows <- getRows usersResult
@@ -263,7 +265,7 @@ let
263void . withConnection "host=localhost port=5432 dbname=exampledb" $
265void . withConnection "host=localhost port=5432 dbname=exampledb" $
264 define setup
266 define setup
265 & pqThen session
267 & pqThen session
266− & thenDefine teardown
268+ & pqThen (define teardown)
268−[User {userName = "Alice", userEmail = Just "alice`gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just role`hotmail.com"}]
270+[User {userName = "Alice", userEmail = Just "alice`gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "carole`hotmail.com"}]