Skip to content
Merged

0.3 #33

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
8ca416a
resolve #27
echatav Apr 9, 2018
8d2df16
omg that looks so nice
echatav Apr 9, 2018
8945616
type safe foreign and primary keys
echatav Apr 9, 2018
91a5465
test self references
echatav Apr 9, 2018
7f55fa5
makes the docs prettier with `ForeignKeyed`
echatav Apr 9, 2018
b9103f4
andThen
echatav Apr 9, 2018
0120fc6
small change
echatav Apr 9, 2018
db940a0
views
echatav Apr 9, 2018
6cb5916
create drop and query views
echatav Apr 9, 2018
fbd7a31
fixing tests
echatav Apr 9, 2018
d01eae4
fix tests
echatav Apr 10, 2018
b95400d
doctest for create view
echatav Apr 10, 2018
875f8f8
simplify a bit
echatav Apr 10, 2018
520e294
more sql types
echatav Apr 10, 2018
d6c0a43
create composite types
echatav Apr 10, 2018
ea020d1
dropType
echatav Apr 10, 2018
d34c70c
decode composite values
echatav Apr 11, 2018
7b65fb1
unshadow
echatav Apr 11, 2018
5677306
move decoder instance code for composite
echatav Apr 11, 2018
0f3aa2f
create type safety
echatav Apr 12, 2018
3148d41
enum decoding
echatav Apr 16, 2018
e7e9693
enum and composite pieces
echatav Apr 16, 2018
3be316a
lots of stuff
echatav Apr 17, 2018
87d962c
introduce PG labels
echatav Apr 26, 2018
0f81c9e
fixes
echatav Apr 26, 2018
d3bfaa5
encode composites
echatav Apr 26, 2018
7018d90
createTypeEnumFromHaskell
echatav Apr 27, 2018
c08a8a6
null_, notNull and values
echatav May 18, 2018
5b9374f
semigroup
echatav May 19, 2018
e538877
generic constructor
echatav May 19, 2018
0df15ea
use generics for reading and showing enums
echatav May 19, 2018
a646215
nulls
echatav May 19, 2018
bd505af
fix docs
echatav May 29, 2018
6d0dd7e
remove `;` from manipulations
echatav May 29, 2018
7555538
embed Haskell types in PGType
echatav Jun 1, 2018
e027ede
createTypeEnumWith
echatav Jun 1, 2018
8f2ed09
ZipAliased
echatav Jun 1, 2018
6fa1d5f
get embedding and some cleanup
echatav Jun 1, 2018
d531baa
More sugary instances
echatav Jun 2, 2018
06a7c05
trim trailing whitespaces
echatav Jun 2, 2018
f0e5644
IsLabel instances for grouped columns
echatav Jun 2, 2018
b65851c
aliased aliases
echatav Jun 2, 2018
d541e42
some docs
echatav Jun 3, 2018
2c7844e
documentation and tests
echatav Jun 3, 2018
1b0f63a
argument docstrings
echatav Jun 3, 2018
5709105
argument strings
echatav Jun 3, 2018
c674451
organization
echatav Jun 3, 2018
c41c284
update cabal file
echatav Jun 3, 2018
06033d9
Found some issues with `TypeExpression`s and `ColumnTypeExpression`s …
echatav Jun 3, 2018
a174b79
failing test
echatav Jun 8, 2018
6e5f5f5
push through adding schema kind
echatav Jun 8, 2018
6e3d875
fixes and tests
echatav Jun 21, 2018
1e229ac
docs changes and newline between definitions
echatav Jun 21, 2018
5f2840c
printSQL and docs
echatav Jun 22, 2018
0a30cfe
manipulation docs
echatav Jun 22, 2018
da4f6c6
query docs
echatav Jun 22, 2018
ddeb75d
renderSQL for Alias
echatav Jun 22, 2018
e013ff3
docs
echatav Jun 22, 2018
48243ab
fix test
echatav Jun 22, 2018
51d36f2
binary docs
echatav Jun 22, 2018
922f826
docs
echatav Jun 22, 2018
b1b34c1
binary docs
echatav Jun 22, 2018
a1b7db8
With ~> From
echatav Jun 22, 2018
27f756b
docs
echatav Jun 22, 2018
2eb541d
haddocks
echatav Jun 22, 2018
0fcdcd0
docs
echatav Jun 23, 2018
125e13a
readme
echatav Jun 23, 2018
bfe3e29
readme
echatav Jun 23, 2018
442d285
fix
echatav Jun 23, 2018
5537408
cabal & stack yaml
echatav Jun 23, 2018
e1850b7
release notes
echatav Jun 23, 2018
82bd2b5
try to fix circle
echatav Jun 25, 2018
83a1c9e
cabal and circle
echatav Jun 25, 2018
c79b77d
circle & stack
echatav Jun 25, 2018
89173fa
release notes views
echatav Jun 25, 2018
aa91c35
Update RELEASE NOTES.md
echatav Jun 25, 2018
d3b55de
Update RELEASE NOTES.md
echatav Jun 25, 2018
0d779f5
Update RELEASE NOTES.md
echatav Jun 25, 2018
e98a73f
Update RELEASE NOTES.md
echatav Jun 25, 2018
dcb532d
Update RELEASE NOTES.md
echatav Jun 25, 2018
df89a4b
Update RELEASE NOTES.md
echatav Jun 25, 2018
8e2dfb7
Update RELEASE NOTES.md
echatav Jun 25, 2018
3c5bfcb
Update RELEASE NOTES.md
echatav Jun 25, 2018
6f83d1a
Update RELEASE NOTES.md
echatav Jun 25, 2018
cad427a
cabal
echatav Jun 25, 2018
d01439b
Merge branch '0.3' of https://github.com/morphismtech/squeal into 0.3
echatav Jun 25, 2018
4404cd2
Update RELEASE NOTES.md
echatav Jun 25, 2018
e6857fc
Update RELEASE NOTES.md
echatav Jun 25, 2018
f43c53d
Update RELEASE NOTES.md
echatav Jun 25, 2018
028fcb7
Update RELEASE NOTES.md
echatav Jun 25, 2018
b5e8b42
Update RELEASE NOTES.md
echatav Jun 26, 2018
fc14f92
Update RELEASE NOTES.md
echatav Jun 26, 2018
b3ccfb0
circle
echatav Jun 26, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
jobs:
build:
docker:
- image: haskell:latest
- image: haskell:8.2.2
- image: circleci/postgres:latest
environment:
POSTGRES_DB: exampledb
Expand Down
143 changes: 77 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ composable and cover a large portion of SQL.
* linear, invertible migrations
* connection pools
* transactions
* views
* composite and enumerated types

## installation

Expand All @@ -62,125 +64,133 @@ Let's see an example!
First, we need some language extensions because Squeal uses modern GHC
features.

```haskell
```Haskell
>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
```

We'll need some imports.

```haskell
```Haskell
>>> import Control.Monad (void)
>>> import Control.Monad.Base (liftBase)
>>> import Data.Int (Int32)
>>> import Data.Text (Text)
>>> import Squeal.PostgreSQL
>>> import Squeal.PostgreSQL.Render
```

We'll use generics to easily convert between Haskell and PostgreSQL values.

```haskell
```Haskell
>>> import qualified Generics.SOP as SOP
>>> import qualified GHC.Generics as GHC
```

The first step is to define the schema of our database. This is where
we use `DataKinds` and `TypeOperators`.

```haskell
```Haskell
>>> :{
type Schema =
'[ "users" :::
'[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "name" ::: 'NoDef :=> 'NotNull 'PGtext
]
, "emails" :::
'[ "pk_emails" ::: 'PrimaryKey '["id"]
, "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
, "email" ::: 'NoDef :=> 'Null 'PGtext
]
]
'[ "users" ::: 'Table (
'[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "name" ::: 'NoDef :=> 'NotNull 'PGtext
])
, "emails" ::: 'Table (
'[ "pk_emails" ::: 'PrimaryKey '["id"]
, "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
] :=>
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
, "email" ::: 'NoDef :=> 'Null 'PGtext
])
]
:}
```

Notice the use of type operators. `:::` is used
to pair an alias `Symbol` with either a `TableType` or a `ColumnType`.
`:=>` is used to pair a `TableConstraint`s with a `ColumnsType`,
Notice the use of type operators.

`:::` is used to pair an alias `GHC.TypeLits.Symbol` with a `SchemumType`,
a `TableConstraint` or a `ColumnType`. It is intended to connote Haskell's `::`
operator.

`:=>` is used to pair `TableConstraints` with a `ColumnsType`,
yielding a `TableType`, or to pair a `ColumnConstraint` with a `NullityType`,
yielding a `ColumnType`.
yielding a `ColumnType`. It is intended to connote Haskell's `=>` operator

Next, we'll write `Definition`s 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
Squeal, a `Definition` like `createTable`, `alterTable` or `dropTable`
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.

```haskell
```Haskell
>>> :{
let
setup :: Definition '[] Schema
setup =
createTable #users
( serial `As` #id :*
(text & notNull) `As` #name :* Nil )
( primaryKey (Column #id :* Nil) `As` #pk_users :* Nil ) >>>
createTable #emails
( serial `As` #id :*
(int & notNull) `As` #user_id :*
text `As` #email :* Nil )
( primaryKey (Column #id :* Nil) `As` #pk_emails :*
foreignKey (Column #user_id :* Nil) #users (Column #id :* Nil)
OnDeleteCascade OnUpdateCascade `As` #fk_user_id :* Nil )
createTable #users
( serial `As` #id :*
(text & notNullable) `As` #name :* Nil )
( primaryKey #id `As` #pk_users :* Nil ) >>>
createTable #emails
( serial `As` #id :*
(int & notNullable) `As` #user_id :*
(text & nullable) `As` #email :* Nil )
( primaryKey #id `As` #pk_emails :*
foreignKey #user_id #users #id
OnDeleteCascade OnUpdateCascade `As` #fk_user_id :* Nil )
:}
```

We can easily see the generated SQL is unsuprising looking.
We can easily see the generated SQL is unsurprising looking.

```haskell
>>> renderDefinition setup
"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);"
```Haskell
>>> printSQL setup
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 NULL, CONSTRAINT "pk_emails" PRIMARY KEY ("id"), CONSTRAINT "fk_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE);
```

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
primary and foreign keys, making them somewhat complex. Our `teardown`
`Definition` is simpler.

```haskell
```Haskell
>>> :{
let
teardown :: Definition Schema '[]
teardown = dropTable #emails >>> dropTable #users
:}
>>> renderDefinition teardown
"DROP TABLE emails; DROP TABLE users;"

>>> printSQL teardown
DROP TABLE "emails";
DROP TABLE "users";
```

Next, we'll write `Manipulation`s to insert data into our two tables.
A `Manipulation` is an `insertRow` (or other inserts), `update`
or `deleteFrom` command and
A `Manipulation` like `insertRow`, `update` or `deleteFrom`
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
field but not for the `id` field. Since it's serial, 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.

```haskell
```Haskell
>>> :{
let
insertUser :: Manipulation Schema '[ 'NotNull 'PGtext ] '[ "fromOnly" ::: 'NotNull 'PGint4 ]
insertUser = insertRow #users
(Default `As` #id :* Set (param @1) `As` #name :* Nil)
OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
:}

>>> :{
let
insertEmail :: Manipulation Schema '[ 'NotNull 'PGint4, 'Null 'PGtext] '[]
Expand All @@ -190,18 +200,19 @@ let
Set (param @2) `As` #email :* Nil )
OnConflictDoNothing (Returning Nil)
:}
>>> renderManipulation insertUser
"INSERT INTO users (id, name) VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING RETURNING id AS fromOnly;"
>>> renderManipulation insertEmail
"INSERT INTO emails (id, user_id, email) VALUES (DEFAULT, ($1 :: int4), ($2 :: text)) ON CONFLICT DO NOTHING;"

>>> printSQL insertUser
INSERT INTO "users" ("id", "name") VALUES (DEFAULT, ($1 :: text)) ON CONFLICT DO NOTHING RETURNING "id" AS "fromOnly"
>>> printSQL insertEmail
INSERT INTO "emails" ("id", "user_id", "email") VALUES (DEFAULT, ($1 :: int4), ($2 :: text)) ON CONFLICT DO NOTHING
```

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.

```haskell
```Haskell
>>> :{
let
getUsers :: Query Schema '[]
Expand All @@ -213,8 +224,9 @@ let
& innerJoin (table (#emails `As` #e))
(#u ! #id .== #e ! #user_id)) )
:}
>>> renderQuery getUsers
"SELECT u.name AS userName, e.email AS userEmail FROM users AS u INNER JOIN emails AS e ON (u.id = e.user_id)"

>>> printSQL getUsers
SELECT "u"."name" AS "userName", "e"."email" AS "userEmail" FROM "users" AS "u" INNER JOIN "emails" AS "e" ON ("u"."id" = "e"."user_id")
```

Now that we've defined the SQL side of things, we'll need a Haskell type
Expand All @@ -223,15 +235,15 @@ for users. We give the type `Generics.SOP.Generic` and
we receive when we run `getUsers`. Notice that the record fields of the
`User` type match the column names of `getUsers`.

```haskell
```Haskell
>>> data User = User { userName :: Text, userEmail :: Maybe Text } deriving (Show, GHC.Generic)
>>> instance SOP.Generic User
>>> instance SOP.HasDatatypeInfo User
```

Let's also create some users to add to the database.

```haskell
```Haskell
>>> :{
let
users :: [User]
Expand All @@ -251,7 +263,7 @@ 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.

```haskell
```Haskell
>>> :{
let
session :: PQ Schema Schema IO ()
Expand All @@ -262,12 +274,11 @@ let
usersResult <- runQuery getUsers
usersRows <- getRows usersResult
liftBase $ print (usersRows :: [User])
:}
>>> :{
void . withConnection "host=localhost port=5432 dbname=exampledb" $
define setup
& pqThen session
& pqThen (define teardown)
in
void . withConnection "host=localhost port=5432 dbname=exampledb" $
define setup
& pqThen session
& pqThen (define teardown)
:}
[User {userName = "Alice", userEmail = Just "alice@gmail.com"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "carole@hotmail.com"}]
```
Loading