on dev help sign in
Update README.md
e50a949Eitan Chatav
README.md+27−25
@@ -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.
19
19
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.
23
23
@@ -56,6 +56,7 @@ start postgres on localhost port `5432` and create a database named `exampledb`.
56## usage
56## usage
57
57
58Let's see an example!
58Let's see an example!
59+
59First, we need some language extensions because Squeal uses modern GHC
60First, we need some language extensions because Squeal uses modern GHC
60features.
61features.
61
62
@@ -82,34 +83,34 @@ We'll use generics to easily convert between Haskell and PostgreSQL values.
82```
83```
83
84
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.
92
87
93```haskell
88```haskell
94>>> :{
89>>> :{
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
100 ]
95 ]
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"]
104 ] :=>
99 ] :=>
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 ]
103 ]
109 ]
104 ]
110:}
105:}
111```
106```
112
107
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+
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.
140
141
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);"
144```
145```
145
146
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
159```
160```
160
161
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
173>>> :{
175>>> :{
174let
176let
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)
189:}
190:}
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;"
194```
195```
195
196
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
202>>> :{
203>>> :{
203let
204let
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)) )
212:}
213:}
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)"
215```
216```
217+
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`.
221
223
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
226```
228```
@@ -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)
267:}
269:}
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"}]
269```
271```