on dev help sign in
Update README.md
commite50a949f6c31107c0bcf1103939e74eeb6576457 authorEitan Chatav <eitan.chatav@gmail.com> dateMon 26 Mar 2018, 19:27 (9 years ago) parentd5b26fe
1 file changed+27−25
README.md+27−25
README.md+27−25
@@ -17,7 +17,7 @@ term somewhat. What I mean is that Squeal embeds both SQL terms and SQL types
1717into Haskell at the term and type levels respectively. This leads to a very high level
1818of type-safety in Squeal.
1919
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
2121data manipulation language and the data definition language; that's `SELECT`,
2222`INSERT`, `UPDATE`, `DELETE`, `WITH`, `CREATE`, `DROP`, and `ALTER` commands.
2323
@@ -56,6 +56,7 @@ start postgres on localhost port `5432` and create a database named `exampledb`.
5656## usage
5757
5858Let's see an example!
59+
5960First, we need some language extensions because Squeal uses modern GHC
6061features.
6162
@@ -82,34 +83,34 @@ We'll use generics to easily convert between Haskell and PostgreSQL values.
8283```
8384
8485The 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−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.
86+we use `DataKinds` and `TypeOperators`.
9287
9388```haskell
9489>>> :{
9590type Schema =
9691 '[ "users" :::
9792 '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
98− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
93+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
9994 , "name" ::: 'NoDef :=> 'NotNull 'PGtext
10095 ]
10196 , "emails" :::
102− '[ "pk_emails" ::: 'PrimaryKey '["id"]
97+ '[ "pk_emails" ::: 'PrimaryKey '["id"]
10398 , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
10499 ] :=>
105− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
100+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
106101 , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
107− , "email" ::: 'NoDef :=> 'Null 'PGtext
102+ , "email" ::: 'NoDef :=> 'Null 'PGtext
108103 ]
109104 ]
110105:}
111106```
112107
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`.
113+
113114Next, we'll write `Definition`s to set up and tear down the schema. In
114115Squeal, a `Definition` is a `createTable`, `alterTable` or `dropTable`
115116command and has two type parameters, corresponding to the schema
@@ -140,7 +141,7 @@ We can easily see the generated SQL is unsuprising looking.
140141
141142```haskell
142143>>> 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);"
144145```
145146
146147Notice that `setup` starts with an empty schema `'[]` and produces `Schema`.
@@ -159,7 +160,8 @@ let
159160```
160161
161162Next, we'll write `Manipulation`s to insert data into our two tables.
162−A `Manipulation` is a `insertInto`, `update` or `deleteFrom` command and
163+A `Manipulation` is an `insertRow` (or other inserts), `update`
164+or `deleteFrom` command and
163165has three type parameters, the schema it refers to, a list of parameters
164166it can take as input, and a list of columns it produces as output. When
165167we insert into the users table, we will need a parameter for the `name`
@@ -172,8 +174,7 @@ of our inserts.
172174```haskell
173175>>> :{
174176let
175− insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ]
176− '[ "fromOnly" ::: 'NotNull 'PGint4 ]
177+ insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ] '[ "fromOnly" ::: 'NotNull 'PGint4 ]
177178 insertUser = insertRow #users
178179 (Default `As` #id :* Set (param `1) `As` #name :* Nil)
179180 OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
@@ -188,9 +189,9 @@ let
188189 OnConflictDoNothing (Returning Nil)
189190:}
190191>>> 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;"
192193>>> 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;"
194195```
195196
196197Next 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
202203>>> :{
203204let
204205 getUsers :: Query Schema '[]
205− '[ "userName" ::: 'NotNull 'PGtext
206− , "userEmail" ::: 'Null 'PGtext ]
206+ '[ "userName" ::: 'NotNull 'PGtext
207+ , "userEmail" ::: 'Null 'PGtext ]
207208 getUsers = select
208209 (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
209210 ( from (table (#users `As` #u)
@@ -211,8 +212,9 @@ let
211212 (#u ! #id .== #e ! #user_id)) )
212213:}
213214>>> 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)"
215216```
217+
216218Now that we've defined the SQL side of things, we'll need a Haskell type
217219for users. We give the type `Generics.SOP.Generic` and
218220`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
220222`User` type match the column names of `getUsers`.
221223
222224```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)
224226>>> instance SOP.Generic User
225227>>> instance SOP.HasDatatypeInfo User
226228```
@@ -253,7 +255,7 @@ let
253255 session :: PQ Schema Schema IO ()
254256 session = do
255257 idResults <- traversePrepared insertUser (Only . userName <$> users)
256− ids <- traverse (fmap fromOnly . getRow (RowNumber 0)) idResults
258+ ids <- traverse (fmap fromOnly . getRow 0) idResults
257259 traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
258260 usersResult <- runQuery getUsers
259261 usersRows <- getRows usersResult
@@ -263,7 +265,7 @@ let
263265void . withConnection "host=localhost port=5432 dbname=exampledb" $
264266 define setup
265267 & pqThen session
266− & thenDefine teardown
268+ & pqThen (define teardown)
267269:}
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"}]
269271```