on dev help sign in
readme
commit125e13a8ac0ea46d69161927db4e75744359319f authorEitan Chatav <eitan.chatav@gmail.com> dateFri 22 Jun 2018, 19:36 (8 years ago) parent0fcdcd0
1 file changed+83−71
README.md+83−71
README.md+83−71
@@ -44,6 +44,8 @@ composable and cover a large portion of SQL.
4444* linear, invertible migrations
4545* connection pools
4646* transactions
47+* views
48+* composite and enumerated types
4749
4850## installation
4951
@@ -62,118 +64,125 @@ Let's see an example!
6264First, we need some language extensions because Squeal uses modern GHC
6365features.
6466
65−```haskell
67+```Haskell
6668>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
6769>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
6870```
6971
7072We'll need some imports.
7173
72−```haskell
74+```Haskell
7375>>> import Control.Monad (void)
7476>>> import Control.Monad.Base (liftBase)
7577>>> import Data.Int (Int32)
7678>>> import Data.Text (Text)
7779>>> import Squeal.PostgreSQL
80+>>> import Squeal.PostgreSQL.Render
7881```
7982
8083We'll use generics to easily convert between Haskell and PostgreSQL values.
8184
82−```haskell
85+```Haskell
8386>>> import qualified Generics.SOP as SOP
8487>>> import qualified GHC.Generics as GHC
8588```
8689
8790The first step is to define the schema of our database. This is where
88−we use `DataKinds` and `TypeOperators`.
91+we use @DataKinds@ and @TypeOperators@.
8992
90−```haskell
93+```Haskell
9194>>> :{
9295type Schema =
93− '[ "users" :::
94− '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
95− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
96− , "name" ::: 'NoDef :=> 'NotNull 'PGtext
97− ]
98− , "emails" :::
99− '[ "pk_emails" ::: 'PrimaryKey '["id"]
100− , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
101− ] :=>
102− '[ "id" ::: 'Def :=> 'NotNull 'PGint4
103− , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
104− , "email" ::: 'NoDef :=> 'Null 'PGtext
105− ]
106− ]
96+ '[ "users" ::: 'Table (
97+ '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
98+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
99+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext
100+ ])
101+ , "emails" ::: 'Table (
102+ '[ "pk_emails" ::: 'PrimaryKey '["id"]
103+ , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
104+ ] :=>
105+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
106+ , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
107+ , "email" ::: 'NoDef :=> 'Null 'PGtext
108+ ])
109+ ]
107110:}
108111```
109112
110−Notice the use of type operators. `:::` is used
111−to pair an alias `Symbol` with either a `TableType` or a `ColumnType`.
112−`:=>` is used to pair a `TableConstraint`s with a `ColumnsType`,
113+Notice the use of type operators.
114+
115+`:::` is used to pair an alias `GHC.TypeLits.Symbol` with a `SchemumType`,
116+a `TableConstraint` or a `ColumnType`. It is intended to connote Haskell's @::@
117+operator.
118+
119+`:=>` is used to pair `TableConstraints` with a `ColumnsType`,
113120yielding a `TableType`, or to pair a `ColumnConstraint` with a `NullityType`,
114−yielding a `ColumnType`.
121+yielding a `ColumnType`. It is intended to connote Haskell's @=>@ operator
115122
116123Next, we'll write `Definition`s to set up and tear down the schema. In
117−Squeal, a `Definition` is a `createTable`, `alterTable` or `dropTable`
118−command and has two type parameters, corresponding to the schema
119−before being run and the schema after. We can compose definitions using
120−`>>>`. Here and in the rest of our commands we make use of overloaded
124+Squeal, a `Definition` like `createTable`, `alterTable` or `dropTable`
125+has two type parameters, corresponding to the schema
126+before being run and the schema after. We can compose definitions using `>>>`.
127+Here and in the rest of our commands we make use of overloaded
121128labels to refer to named tables and columns in our schema.
122129
123−```haskell
130+```Haskell
124131>>> :{
125132let
126133 setup :: Definition '[] Schema
127134 setup =
128− createTable #users
129− ( serial `As` #id :*
130− (text & notNullable) `As` #name :* Nil )
131− ( primaryKey #id `As` #pk_users :* Nil ) >>>
132− createTable #emails
133− ( serial `As` #id :*
134− (int & notNullable) `As` #user_id :*
135− (text & nullable) `As` #email :* Nil )
136− ( primaryKey #id `As` #pk_emails :*
137− foreignKey #user_id #users #id
138− OnDeleteCascade OnUpdateCascade `As` #fk_user_id :* Nil )
135+ createTable #users
136+ ( serial `As` #id :*
137+ (text & notNullable) `As` #name :* Nil )
138+ ( primaryKey #id `As` #pk_users :* Nil ) >>>
139+ createTable #emails
140+ ( serial `As` #id :*
141+ (int & notNullable) `As` #user_id :*
142+ (text & nullable) `As` #email :* Nil )
143+ ( primaryKey #id `As` #pk_emails :*
144+ foreignKey #user_id #users #id
145+ OnDeleteCascade OnUpdateCascade `As` #fk_user_id :* Nil )
139146:}
140147```
141148
142−We can easily see the generated SQL is unsuprising looking.
149+We can easily see the generated SQL is unsurprising looking.
143150
144−```haskell
145−>>> renderDefinition setup
146−"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);"
151+```Haskell
152+>>> printSQL setup
153+CREATE TABLE "users" ("id" serial, "name" text NOT NULL, CONSTRAINT "pk_users" PRIMARY KEY ("id"));
154+CREATE TABLE "emails" ("id" serial, "user_id" int NOT NULL, "email" text NULL, CONSTRAINT "pk_emails" PRIMARY KEY ("id"), CONSTRAINT "fk_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE);
147155```
148156
149−Notice that `setup` starts with an empty schema `'[]` and produces `Schema`.
157+Notice that @setup@ starts with an empty schema @'[]@ and produces @Schema@.
150158In our `createTable` commands we included `TableConstraint`s to define
151−primary and foreign keys, making them somewhat complex. Our tear down
159+primary and foreign keys, making them somewhat complex. Our @teardown@
152160`Definition` is simpler.
153161
154−```haskell
162+```Haskell
155163>>> :{
156164let
157165 teardown :: Definition Schema '[]
158166 teardown = dropTable #emails >>> dropTable #users
159167:}
160−>>> renderDefinition teardown
161−"DROP TABLE emails; DROP TABLE users;"
168+
169+>>> printSQL teardown
170+DROP TABLE "emails";
171+DROP TABLE "users";
162172```
163173
164174Next, we'll write `Manipulation`s to insert data into our two tables.
165−A `Manipulation` is an `insertRow` (or other inserts), `update`
166−or `deleteFrom` command and
175+A `Manipulation` like `insertRow`, `update` or `deleteFrom`
167176has three type parameters, the schema it refers to, a list of parameters
168177it can take as input, and a list of columns it produces as output. When
169−we insert into the users table, we will need a parameter for the `name`
170−field but not for the `id` field. Since it's optional, we can use a default
178+we insert into the users table, we will need a parameter for the @name@
179+field but not for the @id@ field. Since it's serial, we can use a default
171180value. However, since the emails table refers to the users table, we will
172181need to retrieve the user id that the insert generates and insert it into
173182the emails table. Take a careful look at the type and definition of both
174183of our inserts.
175184
176−```haskell
185+```Haskell
177186>>> :{
178187let
179188 insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ] '[ "fromOnly" ::: 'NotNull 'PGint4 ]
@@ -181,6 +190,7 @@ let
181190 (Default `As` #id :* Set (param @1) `As` #name :* Nil)
182191 OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
183192:}
193+
184194>>> :{
185195let
186196 insertEmail :: Manipulation Schema '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
@@ -190,10 +200,11 @@ let
190200 Set (param @2) `As` #email :* Nil )
191201 OnConflictDoNothing (Returning Nil)
192202:}
193−>>> renderManipulation insertUser
194−"INSERT INTO users (id, name) VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING RETURNING id AS fromOnly;"
195−>>> renderManipulation insertEmail
196−"INSERT INTO emails (id, user_id, email) VALUES (DEFAULT, ($1 :: int4), ($2 :: text)) ON CONFLICT DO NOTHING;"
203+
204+>>> printSQL insertUser
205+INSERT INTO "users" ("id", "name") VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING RETURNING "id" AS "fromOnly"
206+>>> printSQL insertEmail
207+INSERT INTO "emails" ("id", "user_id", "email") VALUES (DEFAULT, ($1 :: int4), ($2 :: text)) ON CONFLICT DO NOTHING
197208```
198209
199210Next we write a `Query` to retrieve users from the database. We're not
@@ -201,7 +212,7 @@ interested in the ids here, just the usernames and email addresses. We
201212need to use an inner join to get the right result. A `Query` is like a
202213`Manipulation` with the same kind of type parameters.
203214
204−```haskell
215+```Haskell
205216>>> :{
206217let
207218 getUsers :: Query Schema '[]
@@ -213,17 +224,18 @@ let
213224 & innerJoin (table (#emails `As` #e))
214225 (#u ! #id .== #e ! #user_id)) )
215226:}
216−>>> renderQuery getUsers
217−"SELECT u.name AS userName, e.email AS userEmail FROM users AS u INNER JOIN emails AS e ON (u.id = e.user_id)"
227+
228+>>> printSQL getUsers
229+SELECT "u"."name" AS "userName", "e"."email" AS "userEmail" FROM "users" AS "u" INNER JOIN "emails" AS "e" ON ("u"."id" = "e"."user_id")
218230```
219231
220232Now that we've defined the SQL side of things, we'll need a Haskell type
221233for users. We give the type `Generics.SOP.Generic` and
222234`Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
223−we receive when we run `getUsers`. Notice that the record fields of the
224−`User` type match the column names of `getUsers`.
235+we receive when we run @getUsers@. Notice that the record fields of the
236+@User@ type match the column names of @getUsers@.
225237
226−```haskell
238+```Haskell
227239>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, GHC.Generic)
228240>>> instance SOP.Generic User
229241>>> instance SOP.HasDatatypeInfo User
@@ -231,7 +243,7 @@ we receive when we run `getUsers`. Notice that the record fields of the
231243
232244Let's also create some users to add to the database.
233245
234−```haskell
246+```Haskell
235247>>> :{
236248let
237249 users :: [User]
@@ -251,7 +263,7 @@ the changing schema information through by using the indexed `PQ` monad
251263transformer and when the schema doesn't change we can use `Monad` and
252264`MonadPQ` functionality.
253265
254−```haskell
266+```Haskell
255267>>> :{
256268let
257269 session :: PQ Schema Schema IO ()
@@ -262,12 +274,12 @@ let
262274 usersResult <- runQuery getUsers
263275 usersRows <- getRows usersResult
264276 liftBase $ print (usersRows :: [User])
265−:}
266−>>> :{
267−void . withConnection "host=localhost port=5432 dbname=exampledb" $
268− define setup
269− & pqThen session
270− & pqThen (define teardown)
277+in
278+ void . withConnection "host=localhost port=5432 dbname=exampledb" $
279+ define setup
280+ & pqThen session
281+ & pqThen (define teardown)
271282:}
272283[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "carole@hotmail.com"}]
284+-}
273285```